Skip to content
Merged
1 change: 1 addition & 0 deletions .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ YONETANI Tomokazu <y0n3t4n1@gmail.com> <qhwt+git@les.ath.cx>
YONETANI Tomokazu <y0n3t4n1@gmail.com> <y0netan1@dragonflybsd.org>
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Yi-Jyun Pan <pan93412@gmail.com>
Yoichi NAKAYAMA <yoichi.nakayama@gmail.com>
# the two anonymous contributors are different persons:
anonymous <linux@horizon.com>
anonymous <linux@horizon.net>
Expand Down
4 changes: 4 additions & 0 deletions Documentation/RelNotes/2.56.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
8 changes: 4 additions & 4 deletions Documentation/git-pack-refs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pattern>] [--exclude <pattern>]
[synopsis]
git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]

DESCRIPTION
-----------
Expand Down Expand Up @@ -52,8 +52,8 @@ BUGS
----

Older documentation written before the packed-refs mechanism was
introduced may still say things like ".git/refs/heads/<branch> file
exists" when it means "branch <branch> exists".
introduced may still say things like ".git/refs/heads/_<branch>_ file
exists" when it means "branch _<branch>_ exists".


GIT
Expand Down
14 changes: 7 additions & 7 deletions Documentation/git-refs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
`<new-value>`.

delete::
`delete`::
Delete the given reference. This subcommand mirrors `git update-ref -d`
(see linkgit:git-update-ref[1]). When `<old-value>` is given, the
reference is only deleted after verifying that it currently contains
`<old-value>`.

update::
`update`::
Update the given reference to point at `<new-value>`. If `<old-value>`
is given, the reference is only updated after verifying that it
currently contains `<old-value>`. As a special case, an all-zeroes
`<new-value>` deletes the branch, whereas an all-zeroes `<old-value>`
ensures that the branch does not yet exist.

rename::
`rename`::
Rename the reference `<oldref>` to `<newref>`. 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]).
Expand Down
10 changes: 5 additions & 5 deletions Documentation/pack-refs-options.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
--all::
`--all`::

The command by default packs all tags and refs that are already
packed, and leaves other refs
Expand All @@ -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
Expand All @@ -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 <pattern>::
`--include <pattern>`::

Pack refs based on a `glob(7)` pattern. Repetitions of this option
accumulate inclusion patterns. If a ref is both included in `--include` and
Expand All @@ -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 <pattern>::
`--exclude <pattern>`::

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
Expand Down
39 changes: 22 additions & 17 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/*
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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()
*
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
18 changes: 18 additions & 0 deletions t/t6132-pathspec-exclude.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
28 changes: 28 additions & 0 deletions t/unit-tests/u-dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}