@matheusbsbs / matheus-bagliano / commits / 21bf933928
Fork 0

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 <alipman88@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Aaron Lipman committed Sep 15, 2020 at 22:08 UTC 21bf933928c02372633b88aa6c4d9d71271d42b3
13 files changed +92 -65
Documentation/filters.txt
+4
@@ -1,3 +1,7 @@
1 When combining multiple `--contains` and `--no-contains` filters, only
2 references that contain at least one of the `--contains` commits and
3 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
+3 -5
@@ -11,7 +11,7 @@ SYNOPSIS
11 'git branch' [--color[=<when>] | --no-color] [--show-current]
12 [-v [--abbrev=<length> | --no-abbrev]]
13 [--column[=<options>] | --no-column] [--sort=<key>]
14 - [(--merged | --no-merged) [<commit>]]
14 + [--merged [<commit>]] [--no-merged [<commit>]]
15 [--contains [<commit>]] [--no-contains [<commit>]]
16 [--points-at <object>] [--format=<format>]
17 [(-r | --remotes) | (-a | --all)]
@@ -252,13 +252,11 @@ start-point is either a local or remote-tracking branch.
252
253 --merged [<commit>]::
254 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`.
256
257 --no-merged [<commit>]::
258 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`.
260
261 <branchname>::
262 The name of the branch to create or delete.
Documentation/git-for-each-ref.txt
+3 -5
@@ -11,7 +11,7 @@ SYNOPSIS
11 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
12 [(--sort=<key>)...] [--format=<format>] [<pattern>...]
13 [--points-at=<object>]
14 - (--merged[=<object>] | --no-merged[=<object>])
14 + [--merged[=<object>]] [--no-merged[=<object>]]
15 [--contains[=<object>]] [--no-contains[=<object>]]
16
17 DESCRIPTION
@@ -76,13 +76,11 @@ OPTIONS
76
77 --merged[=<object>]::
78 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).
80
81 --no-merged[=<object>]::
82 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).
84
85 --contains[=<object>]::
86 Only list refs which contain the specified commit (HEAD if not
Documentation/git-tag.txt
+3 -3
@@ -15,7 +15,7 @@ SYNOPSIS
15 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
16 [--points-at <object>] [--column[=<options>] | --no-column]
17 [--create-reflog] [--sort=<key>] [--format=<format>]
18 - [--[no-]merged [<commit>]] [<pattern>...]
18 + [--merged <commit>] [--no-merged <commit>] [<pattern>...]
19 'git tag' -v [--format=<format>] <tagname>...
20
21 DESCRIPTION
@@ -149,11 +149,11 @@ This option is only applicable when listing tags without annotation lines.
149
150 --merged [<commit>]::
151 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).
153
154 --no-merged [<commit>]::
155 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).
157
158 --points-at <object>::
159 Only list tags of the given object (HEAD if not
builtin/branch.c
+3 -3
@@ -26,7 +26,7 @@
26 #include "commit-reach.h"
27
28 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]"),
30 N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
31 N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
32 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 !show_current && !unset_upstream && argc == 0)
689 list = 1;
690
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)
693 list = 1;
694
695 if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
builtin/for-each-ref.c
+1 -1
@@ -9,7 +9,7 @@
9 static char const * const for_each_ref_usage[] = {
10 N_("git for-each-ref [<options>] [<pattern>]"),
11 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>]]"),
13 N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
14 NULL
15 };
builtin/tag.c
+4 -4
@@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = {
26 "\t\t<tagname> [<head>]"),
27 N_("git tag -d <tagname>..."),
28 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>...]"),
30 N_("git tag -v [--format=<format>] <tagname>..."),
31 NULL
32 };
@@ -457,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
457 if (argc == 0)
458 cmdmode = 'l';
459 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)
462 cmdmode = 'l';
463 }
464
@@ -509,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
509 die(_("--no-contains option is only allowed in list mode"));
510 if (filter.points_at.nr)
511 die(_("--points-at option is only allowed in list mode"));
512 - if (filter.merge_commit)
512 + if (filter.reachable_from || filter.unreachable_from)
513 die(_("--merged and --no-merged options are only allowed in list mode"));
514 if (cmdmode == 'd')
515 return for_each_tag_name(argv, delete_tag, NULL);
ref-filter.c
+35 -29
@@ -2167,9 +2167,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
2167 * obtain the commit using the 'oid' available and discard all
2168 * non-commits early. The actual filtering is done later.
2169 */
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);
2173 if (!commit)
2174 return 0;
2175 /* We perform the filtering for the '--contains' option... */
@@ -2231,13 +2231,20 @@ void ref_array_clear(struct ref_array *array)
2231 }
2232 }
2233
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)
2235 {
2236 struct rev_info revs;
2237 int i, old_nr;
2238 - struct ref_filter *filter = ref_cbdata->filter;
2238 struct ref_array *array = ref_cbdata->array;
2239 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;
2248
2249 repo_init_revisions(the_repository, &revs, NULL);
2250
@@ -2247,8 +2254,11 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
2254 to_clear[i] = item->commit;
2255 }
2256
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 + }
2262
2263 revs.limited = 1;
2264 if (prepare_revision_walk(&revs))
@@ -2263,14 +2273,19 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
2273
2274 int is_merged = !!(commit->object.flags & UNINTERESTING);
2275
2266 - if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
2276 + if (is_merged == reachable)
2277 array->items[array->nr++] = array->items[i];
2278 else
2279 free_array_item(item);
2280 }
2281
2282 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 +
2289 free(to_clear);
2290 }
2291
@@ -2322,8 +2337,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
2337 clear_contains_cache(&ref_cbdata.no_contains_cache);
2338
2339 /* 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);
2342
2343 return ret;
2344 }
@@ -2541,31 +2556,22 @@ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
2556 {
2557 struct ref_filter *rf = opt->value;
2558 struct object_id oid;
2544 - int no_merged = starts_with(opt->long_name, "no");
2559 + struct commit *merge_commit;
2560
2561 BUG_ON_OPT_NEG(unset);
2562
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 -
2563 if (get_oid(arg, &oid))
2564 die(_("malformed object name %s"), arg);
2565
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)
2569 return error(_("option `%s' must point to a commit"), opt->long_name);
2570
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 +
2576 return 0;
2577 }
ref-filter.h
+5 -7
@@ -23,6 +23,9 @@
23 #define FILTER_REFS_DETACHED_HEAD 0x0020
24 #define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
25
26 +#define DO_MERGE_FILTER_UNREACHABLE 0
27 +#define DO_MERGE_FILTER_REACHABLE 1
28 +
29 struct atom_value;
30
31 struct ref_sorting {
@@ -54,13 +57,8 @@ struct ref_filter {
57 struct oid_array points_at;
58 struct commit_list *with_commit;
59 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;
62
63 unsigned int with_commit_tag_algo : 1,
64 match_as_path : 1,
t/t3200-branch.sh
-4
@@ -1298,10 +1298,6 @@ test_expect_success '--merged catches invalid object names' '
1298 test_must_fail git branch --merged 0000000000000000000000000000000000000000
1299 '
1300
1301 -test_expect_success '--merged is incompatible with --no-merged' '
1302 - test_must_fail git branch --merged HEAD --no-merged HEAD
1303 -'
1304 -
1301 test_expect_success '--list during rebase' '
1302 test_when_finished "reset_rebase" &&
1303 git checkout master &&
t/t3201-branch-contains.sh
+27
@@ -187,6 +187,16 @@ test_expect_success 'multiple branch --contains' '
187 test_cmp expect actual
188 '
189
190 +test_expect_success 'multiple branch --merged' '
191 + git branch --merged next --merged master >actual &&
192 + cat >expect <<-\EOF &&
193 + master
194 + * next
195 + side
196 + EOF
197 + test_cmp expect actual
198 +'
199 +
200 test_expect_success 'multiple branch --no-contains' '
201 git branch --no-contains side --no-contains side2 >actual &&
202 cat >expect <<-\EOF &&
@@ -195,6 +205,14 @@ test_expect_success 'multiple branch --no-contains' '
205 test_cmp expect actual
206 '
207
208 +test_expect_success 'multiple branch --no-merged' '
209 + git branch --no-merged next --no-merged master >actual &&
210 + cat >expect <<-\EOF &&
211 + side2
212 + EOF
213 + test_cmp expect actual
214 +'
215 +
216 test_expect_success 'branch --contains combined with --no-contains' '
217 git checkout -b seen master &&
218 git merge side &&
@@ -207,6 +225,15 @@ test_expect_success 'branch --contains combined with --no-contains' '
225 test_cmp expect actual
226 '
227
228 +test_expect_success 'branch --merged combined with --no-merged' '
229 + git branch --merged seen --no-merged next >actual &&
230 + cat >expect <<-\EOF &&
231 + * seen
232 + side2
233 + EOF
234 + test_cmp expect actual
235 +'
236 +
237 # We want to set up a case where the walk for the tracking info
238 # of one branch crosses the tip of another branch (and make sure
239 # that the latter walk does not mess up our flag to see if it was
t/t6302-for-each-ref-filter.sh
+2 -2
@@ -437,8 +437,8 @@ test_expect_success 'check %(if:notequals=<string>)' '
437 test_cmp expect actual
438 '
439
440 -test_expect_success '--merged is incompatible with --no-merged' '
441 - test_must_fail git for-each-ref --merged HEAD --no-merged HEAD
440 +test_expect_success '--merged is compatible with --no-merged' '
441 + git for-each-ref --merged HEAD --no-merged HEAD
442 '
443
444 test_expect_success 'validate worktree atom' '
t/t7004-tag.sh
+2 -2
@@ -2015,8 +2015,8 @@ test_expect_success '--merged can be used in non-list mode' '
2015 test_cmp expect actual
2016 '
2017
2018 -test_expect_success '--merged is incompatible with --no-merged' '
2019 - test_must_fail git tag --merged HEAD --no-merged HEAD
2018 +test_expect_success '--merged is compatible with --no-merged' '
2019 + git tag --merged HEAD --no-merged HEAD
2020 '
2021
2022 test_expect_success '--merged shows merged tags' '