pathspec.con commit Merge branch 'pw/no-editor-in-rebase-i-implicit' (69dd6e5)
   1#define NO_THE_INDEX_COMPATIBILITY_MACROS
   2#include "cache.h"
   3#include "config.h"
   4#include "dir.h"
   5#include "pathspec.h"
   6#include "attr.h"
   7
   8/*
   9 * Finds which of the given pathspecs match items in the index.
  10 *
  11 * For each pathspec, sets the corresponding entry in the seen[] array
  12 * (which should be specs items long, i.e. the same size as pathspec)
  13 * to the nature of the "closest" (i.e. most specific) match found for
  14 * that pathspec in the index, if it was a closer type of match than
  15 * the existing entry.  As an optimization, matching is skipped
  16 * altogether if seen[] already only contains non-zero entries.
  17 *
  18 * If seen[] has not already been written to, it may make sense
  19 * to use find_pathspecs_matching_against_index() instead.
  20 */
  21void add_pathspec_matches_against_index(const struct pathspec *pathspec,
  22                                        const struct index_state *istate,
  23                                        char *seen)
  24{
  25        int num_unmatched = 0, i;
  26
  27        /*
  28         * Since we are walking the index as if we were walking the directory,
  29         * we have to mark the matched pathspec as seen; otherwise we will
  30         * mistakenly think that the user gave a pathspec that did not match
  31         * anything.
  32         */
  33        for (i = 0; i < pathspec->nr; i++)
  34                if (!seen[i])
  35                        num_unmatched++;
  36        if (!num_unmatched)
  37                return;
  38        for (i = 0; i < istate->cache_nr; i++) {
  39                const struct cache_entry *ce = istate->cache[i];
  40                ce_path_match(istate, ce, pathspec, seen);
  41        }
  42}
  43
  44/*
  45 * Finds which of the given pathspecs match items in the index.
  46 *
  47 * This is a one-shot wrapper around add_pathspec_matches_against_index()
  48 * which allocates, populates, and returns a seen[] array indicating the
  49 * nature of the "closest" (i.e. most specific) matches which each of the
  50 * given pathspecs achieves against all items in the index.
  51 */
  52char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
  53                                            const struct index_state *istate)
  54{
  55        char *seen = xcalloc(pathspec->nr, 1);
  56        add_pathspec_matches_against_index(pathspec, istate, seen);
  57        return seen;
  58}
  59
  60/*
  61 * Magic pathspec
  62 *
  63 * Possible future magic semantics include stuff like:
  64 *
  65 *      { PATHSPEC_RECURSIVE, '*', "recursive" },
  66 *      { PATHSPEC_REGEXP, '\0', "regexp" },
  67 *
  68 */
  69
  70static struct pathspec_magic {
  71        unsigned bit;
  72        char mnemonic; /* this cannot be ':'! */
  73        const char *name;
  74} pathspec_magic[] = {
  75        { PATHSPEC_FROMTOP,  '/', "top" },
  76        { PATHSPEC_LITERAL, '\0', "literal" },
  77        { PATHSPEC_GLOB,    '\0', "glob" },
  78        { PATHSPEC_ICASE,   '\0', "icase" },
  79        { PATHSPEC_EXCLUDE,  '!', "exclude" },
  80        { PATHSPEC_ATTR,    '\0', "attr" },
  81};
  82
  83static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
  84{
  85        int i;
  86        strbuf_addstr(sb, ":(");
  87        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
  88                if (magic & pathspec_magic[i].bit) {
  89                        if (sb->buf[sb->len - 1] != '(')
  90                                strbuf_addch(sb, ',');
  91                        strbuf_addstr(sb, pathspec_magic[i].name);
  92                }
  93        strbuf_addf(sb, ",prefix:%d)", prefixlen);
  94}
  95
  96static size_t strcspn_escaped(const char *s, const char *stop)
  97{
  98        const char *i;
  99
 100        for (i = s; *i; i++) {
 101                /* skip the escaped character */
 102                if (i[0] == '\\' && i[1]) {
 103                        i++;
 104                        continue;
 105                }
 106
 107                if (strchr(stop, *i))
 108                        break;
 109        }
 110        return i - s;
 111}
 112
 113static inline int invalid_value_char(const char ch)
 114{
 115        if (isalnum(ch) || strchr(",-_", ch))
 116                return 0;
 117        return -1;
 118}
 119
 120static char *attr_value_unescape(const char *value)
 121{
 122        const char *src;
 123        char *dst, *ret;
 124
 125        ret = xmallocz(strlen(value));
 126        for (src = value, dst = ret; *src; src++, dst++) {
 127                if (*src == '\\') {
 128                        if (!src[1])
 129                                die(_("Escape character '\\' not allowed as "
 130                                      "last character in attr value"));
 131                        src++;
 132                }
 133                if (invalid_value_char(*src))
 134                        die("cannot use '%c' for value matching", *src);
 135                *dst = *src;
 136        }
 137        *dst = '\0';
 138        return ret;
 139}
 140
 141static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)
 142{
 143        struct string_list_item *si;
 144        struct string_list list = STRING_LIST_INIT_DUP;
 145
 146        if (item->attr_check || item->attr_match)
 147                die(_("Only one 'attr:' specification is allowed."));
 148
 149        if (!value || !*value)
 150                die(_("attr spec must not be empty"));
 151
 152        string_list_split(&list, value, ' ', -1);
 153        string_list_remove_empty_items(&list, 0);
 154
 155        item->attr_check = attr_check_alloc();
 156        item->attr_match = xcalloc(list.nr, sizeof(struct attr_match));
 157
 158        for_each_string_list_item(si, &list) {
 159                size_t attr_len;
 160                char *attr_name;
 161                const struct git_attr *a;
 162
 163                int j = item->attr_match_nr++;
 164                const char *attr = si->string;
 165                struct attr_match *am = &item->attr_match[j];
 166
 167                switch (*attr) {
 168                case '!':
 169                        am->match_mode = MATCH_UNSPECIFIED;
 170                        attr++;
 171                        attr_len = strlen(attr);
 172                        break;
 173                case '-':
 174                        am->match_mode = MATCH_UNSET;
 175                        attr++;
 176                        attr_len = strlen(attr);
 177                        break;
 178                default:
 179                        attr_len = strcspn(attr, "=");
 180                        if (attr[attr_len] != '=')
 181                                am->match_mode = MATCH_SET;
 182                        else {
 183                                const char *v = &attr[attr_len + 1];
 184                                am->match_mode = MATCH_VALUE;
 185                                am->value = attr_value_unescape(v);
 186                        }
 187                        break;
 188                }
 189
 190                attr_name = xmemdupz(attr, attr_len);
 191                a = git_attr(attr_name);
 192                if (!a)
 193                        die(_("invalid attribute name %s"), attr_name);
 194
 195                attr_check_append(item->attr_check, a);
 196
 197                free(attr_name);
 198        }
 199
 200        if (item->attr_check->nr != item->attr_match_nr)
 201                BUG("should have same number of entries");
 202
 203        string_list_clear(&list, 0);
 204}
 205
 206static inline int get_literal_global(void)
 207{
 208        static int literal = -1;
 209
 210        if (literal < 0)
 211                literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
 212
 213        return literal;
 214}
 215
 216static inline int get_glob_global(void)
 217{
 218        static int glob = -1;
 219
 220        if (glob < 0)
 221                glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
 222
 223        return glob;
 224}
 225
 226static inline int get_noglob_global(void)
 227{
 228        static int noglob = -1;
 229
 230        if (noglob < 0)
 231                noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
 232
 233        return noglob;
 234}
 235
 236static inline int get_icase_global(void)
 237{
 238        static int icase = -1;
 239
 240        if (icase < 0)
 241                icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
 242
 243        return icase;
 244}
 245
 246static int get_global_magic(int element_magic)
 247{
 248        int global_magic = 0;
 249
 250        if (get_literal_global())
 251                global_magic |= PATHSPEC_LITERAL;
 252
 253        /* --glob-pathspec is overridden by :(literal) */
 254        if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL))
 255                global_magic |= PATHSPEC_GLOB;
 256
 257        if (get_glob_global() && get_noglob_global())
 258                die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
 259
 260        if (get_icase_global())
 261                global_magic |= PATHSPEC_ICASE;
 262
 263        if ((global_magic & PATHSPEC_LITERAL) &&
 264            (global_magic & ~PATHSPEC_LITERAL))
 265                die(_("global 'literal' pathspec setting is incompatible "
 266                      "with all other global pathspec settings"));
 267
 268        /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
 269        if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB))
 270                global_magic |= PATHSPEC_LITERAL;
 271
 272        return global_magic;
 273}
 274
 275/*
 276 * Parse the pathspec element looking for long magic
 277 *
 278 * saves all magic in 'magic'
 279 * if prefix magic is used, save the prefix length in 'prefix_len'
 280 * returns the position in 'elem' after all magic has been parsed
 281 */
 282static const char *parse_long_magic(unsigned *magic, int *prefix_len,
 283                                    struct pathspec_item *item,
 284                                    const char *elem)
 285{
 286        const char *pos;
 287        const char *nextat;
 288
 289        for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) {
 290                size_t len = strcspn_escaped(pos, ",)");
 291                int i;
 292
 293                if (pos[len] == ',')
 294                        nextat = pos + len + 1; /* handle ',' */
 295                else
 296                        nextat = pos + len; /* handle ')' and '\0' */
 297
 298                if (!len)
 299                        continue;
 300
 301                if (starts_with(pos, "prefix:")) {
 302                        char *endptr;
 303                        *prefix_len = strtol(pos + 7, &endptr, 10);
 304                        if (endptr - pos != len)
 305                                die(_("invalid parameter for pathspec magic 'prefix'"));
 306                        continue;
 307                }
 308
 309                if (starts_with(pos, "attr:")) {
 310                        char *attr_body = xmemdupz(pos + 5, len - 5);
 311                        parse_pathspec_attr_match(item, attr_body);
 312                        *magic |= PATHSPEC_ATTR;
 313                        free(attr_body);
 314                        continue;
 315                }
 316
 317                for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
 318                        if (strlen(pathspec_magic[i].name) == len &&
 319                            !strncmp(pathspec_magic[i].name, pos, len)) {
 320                                *magic |= pathspec_magic[i].bit;
 321                                break;
 322                        }
 323                }
 324
 325                if (ARRAY_SIZE(pathspec_magic) <= i)
 326                        die(_("Invalid pathspec magic '%.*s' in '%s'"),
 327                            (int) len, pos, elem);
 328        }
 329
 330        if (*pos != ')')
 331                die(_("Missing ')' at the end of pathspec magic in '%s'"),
 332                    elem);
 333        pos++;
 334
 335        return pos;
 336}
 337
 338/*
 339 * Parse the pathspec element looking for short magic
 340 *
 341 * saves all magic in 'magic'
 342 * returns the position in 'elem' after all magic has been parsed
 343 */
 344static const char *parse_short_magic(unsigned *magic, const char *elem)
 345{
 346        const char *pos;
 347
 348        for (pos = elem + 1; *pos && *pos != ':'; pos++) {
 349                char ch = *pos;
 350                int i;
 351
 352                /* Special case alias for '!' */
 353                if (ch == '^') {
 354                        *magic |= PATHSPEC_EXCLUDE;
 355                        continue;
 356                }
 357
 358                if (!is_pathspec_magic(ch))
 359                        break;
 360
 361                for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
 362                        if (pathspec_magic[i].mnemonic == ch) {
 363                                *magic |= pathspec_magic[i].bit;
 364                                break;
 365                        }
 366                }
 367
 368                if (ARRAY_SIZE(pathspec_magic) <= i)
 369                        die(_("Unimplemented pathspec magic '%c' in '%s'"),
 370                            ch, elem);
 371        }
 372
 373        if (*pos == ':')
 374                pos++;
 375
 376        return pos;
 377}
 378
 379static const char *parse_element_magic(unsigned *magic, int *prefix_len,
 380                                       struct pathspec_item *item,
 381                                       const char *elem)
 382{
 383        if (elem[0] != ':' || get_literal_global())
 384                return elem; /* nothing to do */
 385        else if (elem[1] == '(')
 386                /* longhand */
 387                return parse_long_magic(magic, prefix_len, item, elem);
 388        else
 389                /* shorthand */
 390                return parse_short_magic(magic, elem);
 391}
 392
 393/*
 394 * Perform the initialization of a pathspec_item based on a pathspec element.
 395 */
 396static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
 397                               const char *prefix, int prefixlen,
 398                               const char *elt)
 399{
 400        unsigned magic = 0, element_magic = 0;
 401        const char *copyfrom = elt;
 402        char *match;
 403        int pathspec_prefix = -1;
 404
 405        item->attr_check = NULL;
 406        item->attr_match = NULL;
 407        item->attr_match_nr = 0;
 408
 409        /* PATHSPEC_LITERAL_PATH ignores magic */
 410        if (flags & PATHSPEC_LITERAL_PATH) {
 411                magic = PATHSPEC_LITERAL;
 412        } else {
 413                copyfrom = parse_element_magic(&element_magic,
 414                                               &pathspec_prefix,
 415                                               item,
 416                                               elt);
 417                magic |= element_magic;
 418                magic |= get_global_magic(element_magic);
 419        }
 420
 421        item->magic = magic;
 422
 423        if (pathspec_prefix >= 0 &&
 424            (prefixlen || (prefix && *prefix)))
 425                BUG("'prefix' magic is supposed to be used at worktree's root");
 426
 427        if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
 428                die(_("%s: 'literal' and 'glob' are incompatible"), elt);
 429
 430        /* Create match string which will be used for pathspec matching */
 431        if (pathspec_prefix >= 0) {
 432                match = xstrdup(copyfrom);
 433                prefixlen = pathspec_prefix;
 434        } else if (magic & PATHSPEC_FROMTOP) {
 435                match = xstrdup(copyfrom);
 436                prefixlen = 0;
 437        } else {
 438                match = prefix_path_gently(prefix, prefixlen,
 439                                           &prefixlen, copyfrom);
 440                if (!match)
 441                        die(_("%s: '%s' is outside repository"), elt, copyfrom);
 442        }
 443
 444        item->match = match;
 445        item->len = strlen(item->match);
 446        item->prefix = prefixlen;
 447
 448        /*
 449         * Prefix the pathspec (keep all magic) and assign to
 450         * original. Useful for passing to another command.
 451         */
 452        if ((flags & PATHSPEC_PREFIX_ORIGIN) &&
 453            !get_literal_global()) {
 454                struct strbuf sb = STRBUF_INIT;
 455
 456                /* Preserve the actual prefix length of each pattern */
 457                prefix_magic(&sb, prefixlen, element_magic);
 458
 459                strbuf_addstr(&sb, match);
 460                item->original = strbuf_detach(&sb, NULL);
 461        } else {
 462                item->original = xstrdup(elt);
 463        }
 464
 465        if (magic & PATHSPEC_LITERAL) {
 466                item->nowildcard_len = item->len;
 467        } else {
 468                item->nowildcard_len = simple_length(item->match);
 469                if (item->nowildcard_len < prefixlen)
 470                        item->nowildcard_len = prefixlen;
 471        }
 472
 473        item->flags = 0;
 474        if (magic & PATHSPEC_GLOB) {
 475                /*
 476                 * FIXME: should we enable ONESTAR in _GLOB for
 477                 * pattern "* * / * . c"?
 478                 */
 479        } else {
 480                if (item->nowildcard_len < item->len &&
 481                    item->match[item->nowildcard_len] == '*' &&
 482                    no_wildcard(item->match + item->nowildcard_len + 1))
 483                        item->flags |= PATHSPEC_ONESTAR;
 484        }
 485
 486        /* sanity checks, pathspec matchers assume these are sane */
 487        if (item->nowildcard_len > item->len ||
 488            item->prefix         > item->len) {
 489                BUG("error initializing pathspec_item");
 490        }
 491}
 492
 493static int pathspec_item_cmp(const void *a_, const void *b_)
 494{
 495        struct pathspec_item *a, *b;
 496
 497        a = (struct pathspec_item *)a_;
 498        b = (struct pathspec_item *)b_;
 499        return strcmp(a->match, b->match);
 500}
 501
 502static void NORETURN unsupported_magic(const char *pattern,
 503                                       unsigned magic)
 504{
 505        struct strbuf sb = STRBUF_INIT;
 506        int i;
 507        for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
 508                const struct pathspec_magic *m = pathspec_magic + i;
 509                if (!(magic & m->bit))
 510                        continue;
 511                if (sb.len)
 512                        strbuf_addstr(&sb, ", ");
 513
 514                if (m->mnemonic)
 515                        strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"),
 516                                    m->name, m->mnemonic);
 517                else
 518                        strbuf_addf(&sb, "'%s'", m->name);
 519        }
 520        /*
 521         * We may want to substitute "this command" with a command
 522         * name. E.g. when add--interactive dies when running
 523         * "checkout -p"
 524         */
 525        die(_("%s: pathspec magic not supported by this command: %s"),
 526            pattern, sb.buf);
 527}
 528
 529void parse_pathspec(struct pathspec *pathspec,
 530                    unsigned magic_mask, unsigned flags,
 531                    const char *prefix, const char **argv)
 532{
 533        struct pathspec_item *item;
 534        const char *entry = argv ? *argv : NULL;
 535        int i, n, prefixlen, nr_exclude = 0;
 536
 537        memset(pathspec, 0, sizeof(*pathspec));
 538
 539        if (flags & PATHSPEC_MAXDEPTH_VALID)
 540                pathspec->magic |= PATHSPEC_MAXDEPTH;
 541
 542        /* No arguments, no prefix -> no pathspec */
 543        if (!entry && !prefix)
 544                return;
 545
 546        if ((flags & PATHSPEC_PREFER_CWD) &&
 547            (flags & PATHSPEC_PREFER_FULL))
 548                BUG("PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
 549
 550        /* No arguments with prefix -> prefix pathspec */
 551        if (!entry) {
 552                if (flags & PATHSPEC_PREFER_FULL)
 553                        return;
 554
 555                if (!(flags & PATHSPEC_PREFER_CWD))
 556                        BUG("PATHSPEC_PREFER_CWD requires arguments");
 557
 558                pathspec->items = item = xcalloc(1, sizeof(*item));
 559                item->match = xstrdup(prefix);
 560                item->original = xstrdup(prefix);
 561                item->nowildcard_len = item->len = strlen(prefix);
 562                item->prefix = item->len;
 563                pathspec->nr = 1;
 564                return;
 565        }
 566
 567        n = 0;
 568        while (argv[n]) {
 569                if (*argv[n] == '\0')
 570                        die("empty string is not a valid pathspec. "
 571                                  "please use . instead if you meant to match all paths");
 572                n++;
 573        }
 574
 575        pathspec->nr = n;
 576        ALLOC_ARRAY(pathspec->items, n + 1);
 577        item = pathspec->items;
 578        prefixlen = prefix ? strlen(prefix) : 0;
 579
 580        for (i = 0; i < n; i++) {
 581                entry = argv[i];
 582
 583                init_pathspec_item(item + i, flags, prefix, prefixlen, entry);
 584
 585                if (item[i].magic & PATHSPEC_EXCLUDE)
 586                        nr_exclude++;
 587                if (item[i].magic & magic_mask)
 588                        unsupported_magic(entry, item[i].magic & magic_mask);
 589
 590                if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
 591                    has_symlink_leading_path(item[i].match, item[i].len)) {
 592                        die(_("pathspec '%s' is beyond a symbolic link"), entry);
 593                }
 594
 595                if (item[i].nowildcard_len < item[i].len)
 596                        pathspec->has_wildcard = 1;
 597                pathspec->magic |= item[i].magic;
 598        }
 599
 600        /*
 601         * If everything is an exclude pattern, add one positive pattern
 602         * that matches everything. We allocated an extra one for this.
 603         */
 604        if (nr_exclude == n) {
 605                int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
 606                init_pathspec_item(item + n, 0, prefix, plen, "");
 607                pathspec->nr++;
 608        }
 609
 610        if (pathspec->magic & PATHSPEC_MAXDEPTH) {
 611                if (flags & PATHSPEC_KEEP_ORDER)
 612                        BUG("PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
 613                QSORT(pathspec->items, pathspec->nr, pathspec_item_cmp);
 614        }
 615}
 616
 617void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
 618{
 619        int i, j;
 620
 621        *dst = *src;
 622        ALLOC_ARRAY(dst->items, dst->nr);
 623        COPY_ARRAY(dst->items, src->items, dst->nr);
 624
 625        for (i = 0; i < dst->nr; i++) {
 626                struct pathspec_item *d = &dst->items[i];
 627                struct pathspec_item *s = &src->items[i];
 628
 629                d->match = xstrdup(s->match);
 630                d->original = xstrdup(s->original);
 631
 632                ALLOC_ARRAY(d->attr_match, d->attr_match_nr);
 633                COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr);
 634                for (j = 0; j < d->attr_match_nr; j++) {
 635                        const char *value = s->attr_match[j].value;
 636                        d->attr_match[j].value = xstrdup_or_null(value);
 637                }
 638
 639                d->attr_check = attr_check_dup(s->attr_check);
 640        }
 641}
 642
 643void clear_pathspec(struct pathspec *pathspec)
 644{
 645        int i, j;
 646
 647        for (i = 0; i < pathspec->nr; i++) {
 648                free(pathspec->items[i].match);
 649                free(pathspec->items[i].original);
 650
 651                for (j = 0; j < pathspec->items[i].attr_match_nr; j++)
 652                        free(pathspec->items[i].attr_match[j].value);
 653                free(pathspec->items[i].attr_match);
 654
 655                if (pathspec->items[i].attr_check)
 656                        attr_check_free(pathspec->items[i].attr_check);
 657        }
 658
 659        FREE_AND_NULL(pathspec->items);
 660        pathspec->nr = 0;
 661}
 662
 663int match_pathspec_attrs(const struct index_state *istate,
 664                         const char *name, int namelen,
 665                         const struct pathspec_item *item)
 666{
 667        int i;
 668        char *to_free = NULL;
 669
 670        if (name[namelen])
 671                name = to_free = xmemdupz(name, namelen);
 672
 673        git_check_attr(istate, name, item->attr_check);
 674
 675        free(to_free);
 676
 677        for (i = 0; i < item->attr_match_nr; i++) {
 678                const char *value;
 679                int matched;
 680                enum attr_match_mode match_mode;
 681
 682                value = item->attr_check->items[i].value;
 683                match_mode = item->attr_match[i].match_mode;
 684
 685                if (ATTR_TRUE(value))
 686                        matched = (match_mode == MATCH_SET);
 687                else if (ATTR_FALSE(value))
 688                        matched = (match_mode == MATCH_UNSET);
 689                else if (ATTR_UNSET(value))
 690                        matched = (match_mode == MATCH_UNSPECIFIED);
 691                else
 692                        matched = (match_mode == MATCH_VALUE &&
 693                                   !strcmp(item->attr_match[i].value, value));
 694                if (!matched)
 695                        return 0;
 696        }
 697
 698        return 1;
 699}