Skip to content

Commit 21bf933

Browse files
alipman88gitster
authored andcommitted
ref-filter: allow merged and no-merged filters
Enable ref-filter to process multiple merged and no-merged filters, and extend functionality to git branch, git tag and git for-each-ref. This provides an easy way to check for branches that are "graduation candidates:" $ git branch --no-merged master --merged next If passed more than one merged (or more than one no-merged) filter, refs must be reachable from any one of the merged commits, and reachable from none of the no-merged commits. Signed-off-by: Aaron Lipman <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 415af72 commit 21bf933

13 files changed

+92
-65
lines changed

Documentation/filters.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
When combining multiple `--contains` and `--no-contains` filters, only
22
references that contain at least one of the `--contains` commits and
33
contain none of the `--no-contains` commits are shown.
4+
5+
When combining multiple `--merged` and `--no-merged` filters, only
6+
references that are reachable from at least one of the `--merged`
7+
commits and from none of the `--no-merged` commits are shown.

Documentation/git-branch.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SYNOPSIS
1111
'git branch' [--color[=<when>] | --no-color] [--show-current]
1212
[-v [--abbrev=<length> | --no-abbrev]]
1313
[--column[=<options>] | --no-column] [--sort=<key>]
14-
[(--merged | --no-merged) [<commit>]]
14+
[--merged [<commit>]] [--no-merged [<commit>]]
1515
[--contains [<commit>]] [--no-contains [<commit>]]
1616
[--points-at <object>] [--format=<format>]
1717
[(-r | --remotes) | (-a | --all)]
@@ -252,13 +252,11 @@ start-point is either a local or remote-tracking branch.
252252

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

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

263261
<branchname>::
264262
The name of the branch to create or delete.

Documentation/git-for-each-ref.txt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SYNOPSIS
1111
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
1212
[(--sort=<key>)...] [--format=<format>] [<pattern>...]
1313
[--points-at=<object>]
14-
(--merged[=<object>] | --no-merged[=<object>])
14+
[--merged[=<object>]] [--no-merged[=<object>]]
1515
[--contains[=<object>]] [--no-contains[=<object>]]
1616

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

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

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

8785
--contains[=<object>]::
8886
Only list refs which contain the specified commit (HEAD if not

Documentation/git-tag.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ SYNOPSIS
1515
'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
1616
[--points-at <object>] [--column[=<options>] | --no-column]
1717
[--create-reflog] [--sort=<key>] [--format=<format>]
18-
[--[no-]merged [<commit>]] [<pattern>...]
18+
[--merged <commit>] [--no-merged <commit>] [<pattern>...]
1919
'git tag' -v [--format=<format>] <tagname>...
2020

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

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

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

158158
--points-at <object>::
159159
Only list tags of the given object (HEAD if not

builtin/branch.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include "commit-reach.h"
2727

2828
static const char * const builtin_branch_usage[] = {
29-
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
29+
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
3030
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
3131
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
3232
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)
688688
!show_current && !unset_upstream && argc == 0)
689689
list = 1;
690690

691-
if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
692-
filter.no_commit)
691+
if (filter.with_commit || filter.no_commit ||
692+
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
693693
list = 1;
694694

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

builtin/for-each-ref.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
static char const * const for_each_ref_usage[] = {
1010
N_("git for-each-ref [<options>] [<pattern>]"),
1111
N_("git for-each-ref [--points-at <object>]"),
12-
N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"),
12+
N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
1313
N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
1414
NULL
1515
};

builtin/tag.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = {
2626
"\t\t<tagname> [<head>]"),
2727
N_("git tag -d <tagname>..."),
2828
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
29-
"\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
29+
"\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
3030
N_("git tag -v [--format=<format>] <tagname>..."),
3131
NULL
3232
};
@@ -457,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
457457
if (argc == 0)
458458
cmdmode = 'l';
459459
else if (filter.with_commit || filter.no_commit ||
460-
filter.points_at.nr || filter.merge_commit ||
461-
filter.lines != -1)
460+
filter.reachable_from || filter.unreachable_from ||
461+
filter.points_at.nr || filter.lines != -1)
462462
cmdmode = 'l';
463463
}
464464

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

ref-filter.c

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,9 +2167,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
21672167
* obtain the commit using the 'oid' available and discard all
21682168
* non-commits early. The actual filtering is done later.
21692169
*/
2170-
if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) {
2171-
commit = lookup_commit_reference_gently(the_repository, oid,
2172-
1);
2170+
if (filter->reachable_from || filter->unreachable_from ||
2171+
filter->with_commit || filter->no_commit || filter->verbose) {
2172+
commit = lookup_commit_reference_gently(the_repository, oid, 1);
21732173
if (!commit)
21742174
return 0;
21752175
/* We perform the filtering for the '--contains' option... */
@@ -2231,13 +2231,20 @@ void ref_array_clear(struct ref_array *array)
22312231
}
22322232
}
22332233

2234-
static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
2234+
static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata, int reachable)
22352235
{
22362236
struct rev_info revs;
22372237
int i, old_nr;
2238-
struct ref_filter *filter = ref_cbdata->filter;
22392238
struct ref_array *array = ref_cbdata->array;
22402239
struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr);
2240+
struct commit_list *rl;
2241+
2242+
struct commit_list *check_reachable_list = reachable ?
2243+
ref_cbdata->filter->reachable_from :
2244+
ref_cbdata->filter->unreachable_from;
2245+
2246+
if (!check_reachable_list)
2247+
return;
22412248

22422249
repo_init_revisions(the_repository, &revs, NULL);
22432250

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

2250-
filter->merge_commit->object.flags |= UNINTERESTING;
2251-
add_pending_object(&revs, &filter->merge_commit->object, "");
2257+
for (rl = check_reachable_list; rl; rl = rl->next) {
2258+
struct commit *merge_commit = rl->item;
2259+
merge_commit->object.flags |= UNINTERESTING;
2260+
add_pending_object(&revs, &merge_commit->object, "");
2261+
}
22522262

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

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

2266-
if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
2276+
if (is_merged == reachable)
22672277
array->items[array->nr++] = array->items[i];
22682278
else
22692279
free_array_item(item);
22702280
}
22712281

22722282
clear_commit_marks_many(old_nr, to_clear, ALL_REV_FLAGS);
2273-
clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
2283+
2284+
while (check_reachable_list) {
2285+
struct commit *merge_commit = pop_commit(&check_reachable_list);
2286+
clear_commit_marks(merge_commit, ALL_REV_FLAGS);
2287+
}
2288+
22742289
free(to_clear);
22752290
}
22762291

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

23242339
/* Filters that need revision walking */
2325-
if (filter->merge_commit)
2326-
do_merge_filter(&ref_cbdata);
2340+
do_merge_filter(&ref_cbdata, DO_MERGE_FILTER_REACHABLE);
2341+
do_merge_filter(&ref_cbdata, DO_MERGE_FILTER_UNREACHABLE);
23272342

23282343
return ret;
23292344
}
@@ -2541,31 +2556,22 @@ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
25412556
{
25422557
struct ref_filter *rf = opt->value;
25432558
struct object_id oid;
2544-
int no_merged = starts_with(opt->long_name, "no");
2559+
struct commit *merge_commit;
25452560

25462561
BUG_ON_OPT_NEG(unset);
25472562

2548-
if (rf->merge) {
2549-
if (no_merged) {
2550-
return error(_("option `%s' is incompatible with --merged"),
2551-
opt->long_name);
2552-
} else {
2553-
return error(_("option `%s' is incompatible with --no-merged"),
2554-
opt->long_name);
2555-
}
2556-
}
2557-
2558-
rf->merge = no_merged
2559-
? REF_FILTER_MERGED_OMIT
2560-
: REF_FILTER_MERGED_INCLUDE;
2561-
25622563
if (get_oid(arg, &oid))
25632564
die(_("malformed object name %s"), arg);
25642565

2565-
rf->merge_commit = lookup_commit_reference_gently(the_repository,
2566-
&oid, 0);
2567-
if (!rf->merge_commit)
2566+
merge_commit = lookup_commit_reference_gently(the_repository, &oid, 0);
2567+
2568+
if (!merge_commit)
25682569
return error(_("option `%s' must point to a commit"), opt->long_name);
25692570

2571+
if (starts_with(opt->long_name, "no"))
2572+
commit_list_insert(merge_commit, &rf->unreachable_from);
2573+
else
2574+
commit_list_insert(merge_commit, &rf->reachable_from);
2575+
25702576
return 0;
25712577
}

ref-filter.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#define FILTER_REFS_DETACHED_HEAD 0x0020
2424
#define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
2525

26+
#define DO_MERGE_FILTER_UNREACHABLE 0
27+
#define DO_MERGE_FILTER_REACHABLE 1
28+
2629
struct atom_value;
2730

2831
struct ref_sorting {
@@ -54,13 +57,8 @@ struct ref_filter {
5457
struct oid_array points_at;
5558
struct commit_list *with_commit;
5659
struct commit_list *no_commit;
57-
58-
enum {
59-
REF_FILTER_MERGED_NONE = 0,
60-
REF_FILTER_MERGED_INCLUDE,
61-
REF_FILTER_MERGED_OMIT
62-
} merge;
63-
struct commit *merge_commit;
60+
struct commit_list *reachable_from;
61+
struct commit_list *unreachable_from;
6462

6563
unsigned int with_commit_tag_algo : 1,
6664
match_as_path : 1,

t/t3200-branch.sh

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,10 +1298,6 @@ test_expect_success '--merged catches invalid object names' '
12981298
test_must_fail git branch --merged 0000000000000000000000000000000000000000
12991299
'
13001300

1301-
test_expect_success '--merged is incompatible with --no-merged' '
1302-
test_must_fail git branch --merged HEAD --no-merged HEAD
1303-
'
1304-
13051301
test_expect_success '--list during rebase' '
13061302
test_when_finished "reset_rebase" &&
13071303
git checkout master &&

0 commit comments

Comments
 (0)