diff --git a/.mailmap b/.mailmap index e3fab1df9dbfa8..29b48905327aa4 100644 --- a/.mailmap +++ b/.mailmap @@ -312,6 +312,7 @@ YONETANI Tomokazu YONETANI Tomokazu YOSHIFUJI Hideaki Yi-Jyun Pan +Yoichi NAKAYAMA # the two anonymous contributors are different persons: anonymous anonymous diff --git a/Documentation/RelNotes/2.56.0.adoc b/Documentation/RelNotes/2.56.0.adoc index 83d2f90daab35c..7056e990fa49ea 100644 --- a/Documentation/RelNotes/2.56.0.adoc +++ b/Documentation/RelNotes/2.56.0.adoc @@ -906,6 +906,10 @@ Fixes since v2.55 repo_logmsg_reencode() during the rewording operation in 'git history' has been plugged. + * The pathspec matching logic has been updated to avoid out-of-bounds + memory accesses when a negative pathspec is shorter than the common + prefix of positive pathspecs. + * Other code cleanup, docfix, build fix, etc. (merge 026636128f ss/submittingpatches-typofix later to maint). (merge d2af22cc21 jc/rerere-doc-typofix later to maint). diff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc index fde9f2f294e815..69e018d07e8010 100644 --- a/Documentation/git-pack-refs.adoc +++ b/Documentation/git-pack-refs.adoc @@ -7,8 +7,8 @@ git-pack-refs - Pack heads and tags for efficient repository access SYNOPSIS -------- -[verse] -'git pack-refs' [--all] [--no-prune] [--auto] [--include ] [--exclude ] +[synopsis] +git pack-refs [--all] [--no-prune] [--auto] [--include ] [--exclude ] DESCRIPTION ----------- @@ -52,8 +52,8 @@ BUGS ---- Older documentation written before the packed-refs mechanism was -introduced may still say things like ".git/refs/heads/ file -exists" when it means "branch exists". +introduced may still say things like ".git/refs/heads/__ file +exists" when it means "branch __ exists". GIT diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc index 9063892651e478..9dc08cbca9dbd3 100644 --- a/Documentation/git-refs.adoc +++ b/Documentation/git-refs.adoc @@ -54,40 +54,40 @@ These limitations may eventually be lifted. `verify`:: Verify reference database consistency. -list:: +`list`:: List references in the repository with support for filtering, formatting, and sorting. This subcommand is an alias for linkgit:git-for-each-ref[1] and offers identical functionality. -exists:: +`exists`:: Check whether the given reference exists. Returns an exit code of 0 if it does, 2 if it is missing, and 1 in case looking up the reference failed with an error other than the reference being missing. This does not verify whether the reference resolves to an actual object. -optimize:: +`optimize`:: Optimizes references to improve repository performance and reduce disk usage. This subcommand is an alias for linkgit:git-pack-refs[1] and offers identical functionality. -create:: +`create`:: Create the given reference, which must not already exist, pointing at ``. -delete:: +`delete`:: Delete the given reference. This subcommand mirrors `git update-ref -d` (see linkgit:git-update-ref[1]). When `` is given, the reference is only deleted after verifying that it currently contains ``. -update:: +`update`:: Update the given reference to point at ``. If `` is given, the reference is only updated after verifying that it currently contains ``. As a special case, an all-zeroes `` deletes the branch, whereas an all-zeroes `` ensures that the branch does not yet exist. -rename:: +`rename`:: Rename the reference `` to ``. The old reference must exist and the new reference must not yet exist, and both must have a well-formed name (see linkgit:git-check-ref-format[1]). diff --git a/Documentation/pack-refs-options.adoc b/Documentation/pack-refs-options.adoc index 0b11282941bb02..2263648b39d921 100644 --- a/Documentation/pack-refs-options.adoc +++ b/Documentation/pack-refs-options.adoc @@ -1,4 +1,4 @@ ---all:: +`--all`:: The command by default packs all tags and refs that are already packed, and leaves other refs @@ -8,12 +8,12 @@ This option causes all refs to be packed as well, with the exception of hidden refs, broken refs, and symbolic refs. Useful for a repository with many branches of historical interests. ---no-prune:: +`--no-prune`:: The command usually removes loose refs under `$GIT_DIR/refs` hierarchy after packing them. This option tells it not to. ---auto:: +`--auto`:: Pack refs as needed depending on the current state of the ref database. The behavior depends on the ref format used by the repository and may change in the @@ -29,7 +29,7 @@ future. maintains the property that N is at least twice as big as N+1. Only tables that violate this property are compacted. ---include :: +`--include `:: Pack refs based on a `glob(7)` pattern. Repetitions of this option accumulate inclusion patterns. If a ref is both included in `--include` and @@ -38,7 +38,7 @@ tags from being included by default. Symbolic refs and broken refs will never be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear and reset the list of patterns. ---exclude :: +`--exclude `:: Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of diff --git a/dir.c b/dir.c index 95d8a1cce90f77..d896e7be4b1fd2 100644 --- a/dir.c +++ b/dir.c @@ -212,9 +212,10 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen, return match_status; } -static size_t common_prefix_len(const struct pathspec *pathspec) +static size_t common_prefix_len(const struct pathspec *pathspec, + const char **matched_prefix) { - int n; + int n, first = -1; size_t max = 0; /* @@ -237,43 +238,47 @@ static size_t common_prefix_len(const struct pathspec *pathspec) size_t i = 0, len = 0, item_len; if (pathspec->items[n].magic & PATHSPEC_EXCLUDE) continue; + if (first < 0) + first = n; if (pathspec->items[n].magic & PATHSPEC_ICASE) item_len = pathspec->items[n].prefix; else item_len = pathspec->items[n].nowildcard_len; - while (i < item_len && (n == 0 || i < max)) { + while (i < item_len && (n == first || i < max)) { char c = pathspec->items[n].match[i]; - if (c != pathspec->items[0].match[i]) + if (c != pathspec->items[first].match[i]) break; if (c == '/') len = i + 1; i++; } - if (n == 0 || len < max) { + if (n == first || len < max) { max = len; if (!max) break; } } + *matched_prefix = first < 0 ? NULL : pathspec->items[first].match; return max; } /* - * Returns a copy of the longest leading path common among all - * pathspecs. + * Returns a copy of the longest leading path common among all pathspec + * items that are not excluded. */ char *common_prefix(const struct pathspec *pathspec) { - unsigned long len = common_prefix_len(pathspec); + const char *matched_prefix; + size_t len = common_prefix_len(pathspec, &matched_prefix); - return len ? xmemdupz(pathspec->items[0].match, len) : NULL; + return len ? xmemdupz(matched_prefix, len) : NULL; } int fill_directory(struct dir_struct *dir, struct index_state *istate, const struct pathspec *pathspec) { - const char *prefix; + const char *matched_prefix; size_t prefix_len; unsigned exclusive_flags = DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO; @@ -284,11 +289,11 @@ int fill_directory(struct dir_struct *dir, * Calculate common prefix for the pathspec, and * use that to optimize the directory walk */ - prefix_len = common_prefix_len(pathspec); - prefix = prefix_len ? pathspec->items[0].match : ""; + prefix_len = common_prefix_len(pathspec, &matched_prefix); /* Read the directory and prune it */ - read_directory(dir, istate, prefix, prefix_len, pathspec); + read_directory(dir, istate, prefix_len ? matched_prefix : "", + prefix_len, pathspec); return prefix_len; } @@ -394,7 +399,7 @@ static int match_pathspec_item(struct index_state *istate, /* * The normal call pattern is: - * 1. prefix = common_prefix_len(ps); + * 1. prefix = common_prefix_len(ps, &matched_prefix); * 2. prune something, or fill_directory * 3. match_pathspec() * @@ -414,8 +419,8 @@ static int match_pathspec_item(struct index_state *istate, * Normally the caller (common_prefix_len() in fact) does * _exact_ matching on name[-prefix+1..-1] and we do not need * to check that part. Be defensive and check it anyway, in - * case common_prefix_len is changed, or a new caller is - * introduced that does not use common_prefix_len. + * case common_prefix_len() is changed, or a new caller is + * introduced that does not use common_prefix_len(). * * If the penalty turns out too high when prefix is really * long, maybe change it to @@ -593,7 +598,7 @@ static int match_pathspec_with_flags(struct index_state *istate, if (!(ps->magic & PATHSPEC_EXCLUDE) || !positive) return positive; negative = do_match_pathspec(istate, ps, name, namelen, - prefix, seen, + 0, seen, flags | DO_MATCH_EXCLUDE); return negative ? 0 : positive; } diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index 9fdafeb1e907f4..e0c3f73ef07e2a 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -183,6 +183,24 @@ EOF test_cmp expect actual ' +test_expect_success 'negative pathspec shorter than positive pathspec prefix' ' + git ls-files -- sub/sub/ ":(exclude)sub2" >actual && + cat <<-\EOF >expect && + sub/sub/file + sub/sub/sub/file + EOF + test_cmp expect actual +' + +test_expect_success 'exclude is matched against the full path' ' + git ls-files -- sub/sub/ ":(exclude)zzzzzzz" >actual && + cat <<-\EOF >expect && + sub/sub/file + sub/sub/sub/file + EOF + test_cmp expect actual +' + test_expect_success 'multiple exclusions' ' git ls-files -- ":^*/file2" ":^sub2" >actual && cat <<-\EOF >expect && diff --git a/t/unit-tests/u-dir.c b/t/unit-tests/u-dir.c index 2d0adaa39ed3d2..a3442c3d3c4121 100644 --- a/t/unit-tests/u-dir.c +++ b/t/unit-tests/u-dir.c @@ -45,3 +45,31 @@ void test_dir__within_depth(void) } + +void test_dir__common_prefix_skips_excluded_pathspec_items(void) +{ + struct pathspec_item items[] = { + { + .match = "unrelated/path", + .magic = PATHSPEC_EXCLUDE, + .nowildcard_len = 14, + }, + { + .match = "foo/bar", + .nowildcard_len = 7, + }, + { + .match = "foo/baz", + .nowildcard_len = 7, + }, + }; + struct pathspec pathspec = { + .nr = ARRAY_SIZE(items), + .magic = PATHSPEC_EXCLUDE, + .items = items, + }; + char *prefix = common_prefix(&pathspec); + + cl_assert_equal_s(prefix, "foo/"); + free(prefix); +}