@@ -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.
@@ -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
@@ -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
@@ -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 +
@@ -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)
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);
@@ -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,11 +2231,17 @@ void ref_array_clear(struct ref_array *array)
}
}
-static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
+static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata, int reachable)
{
+ struct commit_list *check_reachable_list = reachable ?
+ ref_cbdata->filter->reachable_from :
+ ref_cbdata->filter->unreachable_from;
+
+ if (!check_reachable_list)
+ return;
+
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);
@@ -2247,12 +2253,15 @@ 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 (struct commit_list *rl = check_reachable_list; rl; rl = rl->next) {
+ struct commit *merge_commit = rl->item;
+ merge_commit->object.flags |= UNINTERESTING;
+ add_pending_object(&revs, &merge_commit->object, "");
- revs.limited = 1;
- if (prepare_revision_walk(&revs))
- die(_("revision walk setup failed"));
+ revs.limited = 1;
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+ }
old_nr = array->nr;
array->nr = 0;
@@ -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 == reachable)
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_list) {
+ struct commit *merge_commit = pop_commit(&check_reachable_list);
+ 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
clear_contains_cache(&ref_cbdata.no_contains_cache);
/* Filters that need revision walking */
- if (filter->merge_commit)
- do_merge_filter(&ref_cbdata);
+ do_merge_filter(&ref_cbdata, 1);
+ do_merge_filter(&ref_cbdata, 0);
return ret;
}
@@ -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");
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)
+ struct 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;
}
@@ -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,
@@ -1299,8 +1299,8 @@ 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 '--merged is compatible with --no-merged' '
+ git branch --merged master --no-merged master
'
test_expect_success '--list during rebase' '
@@ -226,6 +226,16 @@ test_expect_success 'multiple branch --contains' '
test_cmp expect actual
'
+test_expect_success 'multiple branch --merged' '
+ git branch --merged next --merged master >actual &&
+ cat >expect <<-\EOF &&
+ feature_a
+ master
+ * next
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'multiple branch --no-contains' '
git branch --no-contains feature_a --no-contains feature_b >actual &&
cat >expect <<-\EOF &&
@@ -234,6 +244,14 @@ test_expect_success 'multiple branch --no-contains' '
test_cmp expect actual
'
+test_expect_success 'multiple branch --no-merged' '
+ git branch --no-merged next --no-merged master >actual &&
+ cat >expect <<-\EOF &&
+ feature_b
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'branch --contains combined with --no-contains' '
git checkout master &&
git merge feature_a &&
@@ -247,4 +265,13 @@ test_expect_success 'branch --contains combined with --no-contains' '
test_cmp expect actual
'
+test_expect_success 'branch --merged combined with --no-merged' '
+ git branch --merged next --no-merged master >actual &&
+ cat >expect <<-\EOF &&
+ feature_b
+ * next
+ EOF
+ test_cmp expect actual
+'
+
test_done
@@ -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' '
@@ -2016,7 +2016,7 @@ test_expect_success '--merged can be used in non-list mode' '
'
test_expect_success '--merged is incompatible with --no-merged' '
- test_must_fail git tag --merged HEAD --no-merged HEAD
+ git tag --merged HEAD --no-merged HEAD
'
test_expect_success '--merged shows merged tags' '
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 --merged next --no-merged master To accomplish this, store the merged and no-merged filters in two commit_list structs (reachable_from and unreachable_from) rather than using a single commit struct (merge_commit). 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> --- Documentation/git-branch.txt | 6 +-- Documentation/git-for-each-ref.txt | 6 +-- Documentation/git-tag.txt | 4 +- builtin/branch.c | 4 +- builtin/tag.c | 6 +-- ref-filter.c | 71 ++++++++++++++++-------------- ref-filter.h | 9 +--- t/t3200-branch.sh | 4 +- t/t3201-branch-contains.sh | 27 ++++++++++++ t/t6302-for-each-ref-filter.sh | 4 +- t/t7004-tag.sh | 2 +- 11 files changed, 83 insertions(+), 60 deletions(-)