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>
This commit is contained in:
Aaron Lipman
2020-09-15 22:08:40 -04:00
committed by Junio C Hamano
parent 415af72b17
commit 21bf933928
13 changed files with 92 additions and 65 deletions

View File

@ -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,20 @@ 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 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 *rl;
struct commit_list *check_reachable_list = reachable ?
ref_cbdata->filter->reachable_from :
ref_cbdata->filter->unreachable_from;
if (!check_reachable_list)
return;
repo_init_revisions(the_repository, &revs, NULL);
@ -2247,8 +2254,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 (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))
@ -2263,14 +2273,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 +2337,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, DO_MERGE_FILTER_REACHABLE);
do_merge_filter(&ref_cbdata, DO_MERGE_FILTER_UNREACHABLE);
return ret;
}
@ -2541,31 +2556,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;
}