Browse Source

Merge branch 'al/ref-filter-merged-and-no-merged'

"git for-each-ref" and friends that list refs used to allow only
one --merged or --no-merged to filter them; they learned to take
combination of both kind of filtering.

* al/ref-filter-merged-and-no-merged:
  Doc: prefer more specific file name
  ref-filter: make internal reachable-filter API more precise
  ref-filter: allow merged and no-merged filters
  Doc: cover multiple contains/no-contains filters
  t3201: test multiple branch filter combinations
maint
Junio C Hamano 4 years ago
parent
commit
26a3728bed
  1. 10
      Documentation/git-branch.txt
  2. 13
      Documentation/git-for-each-ref.txt
  3. 11
      Documentation/git-tag.txt
  4. 7
      Documentation/ref-reachability-filters.txt
  5. 6
      builtin/branch.c
  6. 2
      builtin/for-each-ref.c
  7. 8
      builtin/tag.c
  8. 65
      ref-filter.c
  9. 9
      ref-filter.h
  10. 4
      t/t3200-branch.sh
  11. 74
      t/t3201-branch-contains.sh
  12. 4
      t/t6302-for-each-ref-filter.sh
  13. 4
      t/t7004-tag.sh

10
Documentation/git-branch.txt

@ -11,7 +11,7 @@ SYNOPSIS @@ -11,7 +11,7 @@ SYNOPSIS
'git branch' [--color[=<when>] | --no-color] [--show-current]
[-v [--abbrev=<length> | --no-abbrev]]
[--column[=<options>] | --no-column] [--sort=<key>]
[(--merged | --no-merged) [<commit>]]
[--merged [<commit>]] [--no-merged [<commit>]]
[--contains [<commit>]] [--no-contains [<commit>]]
[--points-at <object>] [--format=<format>]
[(-r | --remotes) | (-a | --all)]
@ -252,13 +252,11 @@ start-point is either a local or remote-tracking branch. @@ -252,13 +252,11 @@ start-point is either a local or remote-tracking branch.

--merged [<commit>]::
Only list branches whose tips are reachable from the
specified commit (HEAD if not specified). Implies `--list`,
incompatible with `--no-merged`.
specified commit (HEAD if not specified). Implies `--list`.

--no-merged [<commit>]::
Only list branches whose tips are not reachable from the
specified commit (HEAD if not specified). Implies `--list`,
incompatible with `--merged`.
specified commit (HEAD if not specified). Implies `--list`.

<branchname>::
The name of the branch to create or delete.
@ -370,6 +368,8 @@ serve four related but different purposes: @@ -370,6 +368,8 @@ serve four related but different purposes:
- `--no-merged` is used to find branches which are candidates for merging
into HEAD, since those branches are not fully contained by HEAD.

include::ref-reachability-filters.txt[]

SEE ALSO
--------
linkgit:git-check-ref-format[1],

13
Documentation/git-for-each-ref.txt

@ -11,7 +11,7 @@ SYNOPSIS @@ -11,7 +11,7 @@ SYNOPSIS
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
[(--sort=<key>)...] [--format=<format>] [<pattern>...]
[--points-at=<object>]
(--merged[=<object>] | --no-merged[=<object>])
[--merged[=<object>]] [--no-merged[=<object>]]
[--contains[=<object>]] [--no-contains[=<object>]]

DESCRIPTION
@ -76,13 +76,11 @@ OPTIONS @@ -76,13 +76,11 @@ OPTIONS

--merged[=<object>]::
Only list refs whose tips are reachable from the
specified commit (HEAD if not specified),
incompatible with `--no-merged`.
specified commit (HEAD if not specified).

--no-merged[=<object>]::
Only list refs whose tips are not reachable from the
specified commit (HEAD if not specified),
incompatible with `--merged`.
specified commit (HEAD if not specified).

--contains[=<object>]::
Only list refs which contain the specified commit (HEAD if not
@ -408,6 +406,11 @@ Note also that multiple copies of an object may be present in the object @@ -408,6 +406,11 @@ Note also that multiple copies of an object may be present in the object
database; in this case, it is undefined which copy's size or delta base
will be reported.

NOTES
-----

include::ref-reachability-filters.txt[]

SEE ALSO
--------
linkgit:git-show-ref[1]

11
Documentation/git-tag.txt

@ -15,7 +15,7 @@ SYNOPSIS @@ -15,7 +15,7 @@ SYNOPSIS
'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
[--points-at <object>] [--column[=<options>] | --no-column]
[--create-reflog] [--sort=<key>] [--format=<format>]
[--[no-]merged [<commit>]] [<pattern>...]
[--merged <commit>] [--no-merged <commit>] [<pattern>...]
'git tag' -v [--format=<format>] <tagname>...

DESCRIPTION
@ -149,11 +149,11 @@ This option is only applicable when listing tags without annotation lines. @@ -149,11 +149,11 @@ This option is only applicable when listing tags without annotation lines.

--merged [<commit>]::
Only list tags whose commits are reachable from the specified
commit (`HEAD` if not specified), incompatible with `--no-merged`.
commit (`HEAD` if not specified).

--no-merged [<commit>]::
Only list tags whose commits are not reachable from the specified
commit (`HEAD` if not specified), incompatible with `--merged`.
commit (`HEAD` if not specified).

--points-at <object>::
Only list tags of the given object (HEAD if not
@ -377,6 +377,11 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 @@ -377,6 +377,11 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1

include::date-formats.txt[]

NOTES
-----

include::ref-reachability-filters.txt[]

SEE ALSO
--------
linkgit:git-check-ref-format[1].

7
Documentation/ref-reachability-filters.txt

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
When combining multiple `--contains` and `--no-contains` filters, only
references that contain at least one of the `--contains` commits and
contain none of the `--no-contains` commits are shown.

When combining multiple `--merged` and `--no-merged` filters, only
references that are reachable from at least one of the `--merged`
commits and from none of the `--no-merged` commits are shown.

6
builtin/branch.c

@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
#include "commit-reach.h"

static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
@ -688,8 +688,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) @@ -688,8 +688,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
!show_current && !unset_upstream && argc == 0)
list = 1;

if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
filter.no_commit)
if (filter.with_commit || filter.no_commit ||
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;

if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +

2
builtin/for-each-ref.c

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
static char const * const for_each_ref_usage[] = {
N_("git for-each-ref [<options>] [<pattern>]"),
N_("git for-each-ref [--points-at <object>]"),
N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"),
N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
NULL
};

8
builtin/tag.c

@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = { @@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = {
"\t\t<tagname> [<head>]"),
N_("git tag -d <tagname>..."),
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
"\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
"\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
N_("git tag -v [--format=<format>] <tagname>..."),
NULL
};
@ -457,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) @@ -457,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (argc == 0)
cmdmode = 'l';
else if (filter.with_commit || filter.no_commit ||
filter.points_at.nr || filter.merge_commit ||
filter.lines != -1)
filter.reachable_from || filter.unreachable_from ||
filter.points_at.nr || filter.lines != -1)
cmdmode = 'l';
}

@ -509,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) @@ -509,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die(_("--no-contains option is only allowed in list mode"));
if (filter.points_at.nr)
die(_("--points-at option is only allowed in list mode"));
if (filter.merge_commit)
if (filter.reachable_from || filter.unreachable_from)
die(_("--merged and --no-merged options are only allowed in list mode"));
if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag, NULL);

65
ref-filter.c

@ -2167,9 +2167,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, @@ -2167,9 +2167,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
* obtain the commit using the 'oid' available and discard all
* non-commits early. The actual filtering is done later.
*/
if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) {
commit = lookup_commit_reference_gently(the_repository, oid,
1);
if (filter->reachable_from || filter->unreachable_from ||
filter->with_commit || filter->no_commit || filter->verbose) {
commit = lookup_commit_reference_gently(the_repository, oid, 1);
if (!commit)
return 0;
/* We perform the filtering for the '--contains' option... */
@ -2231,13 +2231,19 @@ void ref_array_clear(struct ref_array *array) @@ -2231,13 +2231,19 @@ void ref_array_clear(struct ref_array *array)
}
}

static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
#define EXCLUDE_REACHED 0
#define INCLUDE_REACHED 1
static void reach_filter(struct ref_array *array,
struct commit_list *check_reachable,
int include_reached)
{
struct rev_info revs;
int i, old_nr;
struct ref_filter *filter = ref_cbdata->filter;
struct ref_array *array = ref_cbdata->array;
struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
struct commit_list *cr;

if (!check_reachable)
return;

repo_init_revisions(the_repository, &revs, NULL);

@ -2247,8 +2253,11 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) @@ -2247,8 +2253,11 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
to_clear[i] = item->commit;
}

filter->merge_commit->object.flags |= UNINTERESTING;
add_pending_object(&revs, &filter->merge_commit->object, "");
for (cr = check_reachable; cr; cr = cr->next) {
struct commit *merge_commit = cr->item;
merge_commit->object.flags |= UNINTERESTING;
add_pending_object(&revs, &merge_commit->object, "");
}

revs.limited = 1;
if (prepare_revision_walk(&revs))
@ -2263,14 +2272,19 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) @@ -2263,14 +2272,19 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)

int is_merged = !!(commit->object.flags & UNINTERESTING);

if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
if (is_merged == include_reached)
array->items[array->nr++] = array->items[i];
else
free_array_item(item);
}

clear_commit_marks_many(old_nr, to_clear, ALL_REV_FLAGS);
clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);

while (check_reachable) {
struct commit *merge_commit = pop_commit(&check_reachable);
clear_commit_marks(merge_commit, ALL_REV_FLAGS);
}

free(to_clear);
}

@ -2322,8 +2336,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int @@ -2322,8 +2336,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
clear_contains_cache(&ref_cbdata.no_contains_cache);

/* Filters that need revision walking */
if (filter->merge_commit)
do_merge_filter(&ref_cbdata);
reach_filter(array, filter->reachable_from, INCLUDE_REACHED);
reach_filter(array, filter->unreachable_from, EXCLUDE_REACHED);

return ret;
}
@ -2541,31 +2555,22 @@ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset) @@ -2541,31 +2555,22 @@ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
{
struct ref_filter *rf = opt->value;
struct object_id oid;
int no_merged = starts_with(opt->long_name, "no");
struct commit *merge_commit;

BUG_ON_OPT_NEG(unset);

if (rf->merge) {
if (no_merged) {
return error(_("option `%s' is incompatible with --merged"),
opt->long_name);
} else {
return error(_("option `%s' is incompatible with --no-merged"),
opt->long_name);
}
}

rf->merge = no_merged
? REF_FILTER_MERGED_OMIT
: REF_FILTER_MERGED_INCLUDE;

if (get_oid(arg, &oid))
die(_("malformed object name %s"), arg);

rf->merge_commit = lookup_commit_reference_gently(the_repository,
&oid, 0);
if (!rf->merge_commit)
merge_commit = lookup_commit_reference_gently(the_repository, &oid, 0);

if (!merge_commit)
return error(_("option `%s' must point to a commit"), opt->long_name);

if (starts_with(opt->long_name, "no"))
commit_list_insert(merge_commit, &rf->unreachable_from);
else
commit_list_insert(merge_commit, &rf->reachable_from);

return 0;
}

9
ref-filter.h

@ -54,13 +54,8 @@ struct ref_filter { @@ -54,13 +54,8 @@ struct ref_filter {
struct oid_array points_at;
struct commit_list *with_commit;
struct commit_list *no_commit;

enum {
REF_FILTER_MERGED_NONE = 0,
REF_FILTER_MERGED_INCLUDE,
REF_FILTER_MERGED_OMIT
} merge;
struct commit *merge_commit;
struct commit_list *reachable_from;
struct commit_list *unreachable_from;

unsigned int with_commit_tag_algo : 1,
match_as_path : 1,

4
t/t3200-branch.sh

@ -1299,10 +1299,6 @@ test_expect_success '--merged catches invalid object names' ' @@ -1299,10 +1299,6 @@ test_expect_success '--merged catches invalid object names' '
test_must_fail git branch --merged 0000000000000000000000000000000000000000
'

test_expect_success '--merged is incompatible with --no-merged' '
test_must_fail git branch --merged HEAD --no-merged HEAD
'

test_expect_success '--list during rebase' '
test_when_finished "reset_rebase" &&
git checkout master &&

74
t/t3201-branch-contains.sh

@ -171,6 +171,69 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b @@ -171,6 +171,69 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b
test_must_fail git branch --no-contains $blob
'

test_expect_success 'multiple branch --contains' '
git checkout -b side2 master &&
>feature &&
git add feature &&
git commit -m "add feature" &&
git checkout -b next master &&
git merge side &&
git branch --contains side --contains side2 >actual &&
cat >expect <<-\EOF &&
* next
side
side2
EOF
test_cmp expect actual
'

test_expect_success 'multiple branch --merged' '
git branch --merged next --merged master >actual &&
cat >expect <<-\EOF &&
master
* next
side
EOF
test_cmp expect actual
'

test_expect_success 'multiple branch --no-contains' '
git branch --no-contains side --no-contains side2 >actual &&
cat >expect <<-\EOF &&
master
EOF
test_cmp expect actual
'

test_expect_success 'multiple branch --no-merged' '
git branch --no-merged next --no-merged master >actual &&
cat >expect <<-\EOF &&
side2
EOF
test_cmp expect actual
'

test_expect_success 'branch --contains combined with --no-contains' '
git checkout -b seen master &&
git merge side &&
git merge side2 &&
git branch --contains side --no-contains side2 >actual &&
cat >expect <<-\EOF &&
next
side
EOF
test_cmp expect actual
'

test_expect_success 'branch --merged combined with --no-merged' '
git branch --merged seen --no-merged next >actual &&
cat >expect <<-\EOF &&
* seen
side2
EOF
test_cmp expect actual
'

# We want to set up a case where the walk for the tracking info
# of one branch crosses the tip of another branch (and make sure
# that the latter walk does not mess up our flag to see if it was
@ -200,15 +263,4 @@ test_expect_success 'branch --merged with --verbose' ' @@ -200,15 +263,4 @@ test_expect_success 'branch --merged with --verbose' '
test_i18ncmp expect actual
'

test_expect_success 'branch --contains combined with --no-contains' '
git branch --contains zzz --no-contains topic >actual &&
cat >expect <<-\EOF &&
master
side
zzz
EOF
test_cmp expect actual

'

test_done

4
t/t6302-for-each-ref-filter.sh

@ -437,8 +437,8 @@ test_expect_success 'check %(if:notequals=<string>)' ' @@ -437,8 +437,8 @@ test_expect_success 'check %(if:notequals=<string>)' '
test_cmp expect actual
'

test_expect_success '--merged is incompatible with --no-merged' '
test_must_fail git for-each-ref --merged HEAD --no-merged HEAD
test_expect_success '--merged is compatible with --no-merged' '
git for-each-ref --merged HEAD --no-merged HEAD
'

test_expect_success 'validate worktree atom' '

4
t/t7004-tag.sh

@ -2015,8 +2015,8 @@ test_expect_success '--merged can be used in non-list mode' ' @@ -2015,8 +2015,8 @@ test_expect_success '--merged can be used in non-list mode' '
test_cmp expect actual
'

test_expect_success '--merged is incompatible with --no-merged' '
test_must_fail git tag --merged HEAD --no-merged HEAD
test_expect_success '--merged is compatible with --no-merged' '
git tag --merged HEAD --no-merged HEAD
'

test_expect_success '--merged shows merged tags' '

Loading…
Cancel
Save