Merge 'kn/for-each-tag-branch' into kn/for-each-tag
* kn/for-each-tag-branch: for-each-ref: add '--contains' option ref-filter: implement '--contains' option parse-options.h: add macros for '--contains' option parse-option: rename parse_opt_with_commit() for-each-ref: add '--merged' and '--no-merged' options ref-filter: implement '--merged' and '--no-merged' options ref-filter: add parse_opt_merge_filter() for-each-ref: add '--points-at' option ref-filter: implement '--points-at' option tag: libify parse_opt_points_at() t6302: for-each-ref tests for ref-filter APIs
This commit is contained in:
commit
a123b19eec
@ -10,6 +10,8 @@ SYNOPSIS
|
||||
[verse]
|
||||
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
|
||||
[(--sort=<key>)...] [--format=<format>] [<pattern>...]
|
||||
[--points-at <object>] [(--merged | --no-merged) [<object>]]
|
||||
[--contains [<object>]]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -62,6 +64,20 @@ OPTIONS
|
||||
the specified host language. This is meant to produce
|
||||
a scriptlet that can directly be `eval`ed.
|
||||
|
||||
--points-at <object>::
|
||||
Only list refs which points at the given object.
|
||||
|
||||
--merged [<object>]::
|
||||
Only list refs whose tips are reachable from the
|
||||
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).
|
||||
|
||||
--contains [<object>]::
|
||||
Only list tags which contain the specified commit (HEAD if not
|
||||
specified).
|
||||
|
||||
FIELD NAMES
|
||||
-----------
|
||||
|
@ -636,6 +636,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
|
||||
cb.pattern = pattern;
|
||||
cb.ret = 0;
|
||||
for_each_rawref(append_ref, &cb);
|
||||
/*
|
||||
* The following implementation is currently duplicated in ref-filter. It
|
||||
* will eventually be removed when we port branch.c to use ref-filter APIs.
|
||||
*/
|
||||
if (merge_filter != NO_FILTER) {
|
||||
struct commit *filter;
|
||||
filter = lookup_commit_reference_gently(merge_filter_ref, 0);
|
||||
@ -746,6 +750,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
|
||||
strbuf_release(&newsection);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is duplicated in ref-filter. It will eventually be removed
|
||||
* when we port branch.c to use ref-filter APIs.
|
||||
*/
|
||||
static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
merge_filter = ((opt->long_name[0] == 'n')
|
||||
@ -821,18 +829,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
||||
OPT__COLOR(&branch_use_color, N_("use colored output")),
|
||||
OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"),
|
||||
REF_REMOTE_BRANCH),
|
||||
{
|
||||
OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
|
||||
N_("print only branches that contain the commit"),
|
||||
PARSE_OPT_LASTARG_DEFAULT,
|
||||
parse_opt_with_commit, (intptr_t)"HEAD",
|
||||
},
|
||||
{
|
||||
OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
|
||||
N_("print only branches that contain the commit"),
|
||||
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
|
||||
parse_opt_with_commit, (intptr_t) "HEAD",
|
||||
},
|
||||
OPT_CONTAINS(&with_commit, N_("print only branches that contain the commit")),
|
||||
OPT_WITH(&with_commit, N_("print only branches that contain the commit")),
|
||||
OPT__ABBREV(&abbrev),
|
||||
|
||||
OPT_GROUP(N_("Specific git-branch actions:")),
|
||||
|
@ -7,6 +7,9 @@
|
||||
|
||||
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) [<object>]]"),
|
||||
N_("git for-each-ref [--contains [<object>]]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -34,9 +37,18 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||
OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
|
||||
OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
|
||||
N_("field name to sort on"), &parse_opt_ref_sorting),
|
||||
OPT_CALLBACK(0, "points-at", &filter.points_at,
|
||||
N_("object"), N_("print only refs which points at the given object"),
|
||||
parse_opt_object_name),
|
||||
OPT_MERGED(&filter, N_("print only refs that are merged")),
|
||||
OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
|
||||
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
memset(&array, 0, sizeof(array));
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
|
||||
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
|
||||
if (maxcount < 0) {
|
||||
error("invalid --count argument: `%d'", maxcount);
|
||||
@ -55,8 +67,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
||||
/* for warn_ambiguous_refs */
|
||||
git_config(git_default_config, NULL);
|
||||
|
||||
memset(&array, 0, sizeof(array));
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
filter.name_patterns = argv;
|
||||
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
|
||||
ref_array_sort(sorting, &array);
|
||||
|
@ -56,6 +56,10 @@ static int match_pattern(const char **patterns, const char *ref)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is currently duplicated in ref-filter.c, and will eventually be
|
||||
* removed as we port tag.c to use the ref-filter APIs.
|
||||
*/
|
||||
static const unsigned char *match_points_at(const char *refname,
|
||||
const unsigned char *sha1)
|
||||
{
|
||||
@ -82,6 +86,11 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The entire code segment for supporting the --contains option has been
|
||||
* copied over to ref-filter.{c,h}. This will be deleted evetually when
|
||||
* we port tag.c to use ref-filter APIs.
|
||||
*/
|
||||
enum contains_result {
|
||||
CONTAINS_UNKNOWN = -1,
|
||||
CONTAINS_NO = 0,
|
||||
@ -546,23 +555,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
|
||||
return check_refname_format(sb->buf, 0);
|
||||
}
|
||||
|
||||
static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
|
||||
const char *arg, int unset)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
|
||||
if (unset) {
|
||||
sha1_array_clear(&points_at);
|
||||
return 0;
|
||||
}
|
||||
if (!arg)
|
||||
return error(_("switch 'points-at' requires an object"));
|
||||
if (get_sha1(arg, sha1))
|
||||
return error(_("malformed object name '%s'"), arg);
|
||||
sha1_array_append(&points_at, sha1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
int *sort = opt->value;
|
||||
@ -610,25 +602,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||
|
||||
OPT_GROUP(N_("Tag listing options")),
|
||||
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
|
||||
OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")),
|
||||
OPT_WITH(&with_commit, N_("print only tags that contain the commit")),
|
||||
{
|
||||
OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
|
||||
PARSE_OPT_NONEG, parse_opt_sort
|
||||
},
|
||||
{
|
||||
OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
|
||||
N_("print only tags that contain the commit"),
|
||||
PARSE_OPT_LASTARG_DEFAULT,
|
||||
parse_opt_with_commit, (intptr_t)"HEAD",
|
||||
},
|
||||
{
|
||||
OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
|
||||
N_("print only tags that contain the commit"),
|
||||
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
|
||||
parse_opt_with_commit, (intptr_t)"HEAD",
|
||||
},
|
||||
{
|
||||
OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
|
||||
N_("print only tags of the object"), 0, parse_opt_points_at
|
||||
OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"),
|
||||
N_("print only tags of the object"), 0, parse_opt_object_name
|
||||
},
|
||||
OPT_END()
|
||||
};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "color.h"
|
||||
#include "string-list.h"
|
||||
#include "argv-array.h"
|
||||
#include "sha1-array.h"
|
||||
|
||||
/*----- some often used options -----*/
|
||||
|
||||
@ -77,7 +78,7 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
|
||||
int parse_opt_commits(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
struct commit *commit;
|
||||
@ -93,6 +94,22 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_opt_object_name(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
|
||||
if (unset) {
|
||||
sha1_array_clear(opt->value);
|
||||
return 0;
|
||||
}
|
||||
if (!arg)
|
||||
return -1;
|
||||
if (get_sha1(arg, sha1))
|
||||
return error(_("malformed object name '%s'"), arg);
|
||||
sha1_array_append(opt->value, sha1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
int *target = opt->value;
|
||||
|
@ -223,7 +223,8 @@ extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
|
||||
extern int parse_opt_expiry_date_cb(const struct option *, const char *, int);
|
||||
extern int parse_opt_color_flag_cb(const struct option *, const char *, int);
|
||||
extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
|
||||
extern int parse_opt_with_commit(const struct option *, const char *, int);
|
||||
extern int parse_opt_object_name(const struct option *, const char *, int);
|
||||
extern int parse_opt_commits(const struct option *, const char *, int);
|
||||
extern int parse_opt_tertiary(const struct option *, const char *, int);
|
||||
extern int parse_opt_string_list(const struct option *, const char *, int);
|
||||
extern int parse_opt_noop_cb(const struct option *, const char *, int);
|
||||
@ -251,5 +252,12 @@ extern int parse_opt_passthru_argv(const struct option *, const char *, int);
|
||||
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru }
|
||||
#define OPT_PASSTHRU_ARGV(s, l, v, a, h, f) \
|
||||
{ OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), parse_opt_passthru_argv }
|
||||
#define _OPT_CONTAINS_OR_WITH(name, variable, help, flag) \
|
||||
{ OPTION_CALLBACK, 0, name, (variable), N_("commit"), (help), \
|
||||
PARSE_OPT_LASTARG_DEFAULT | flag, \
|
||||
parse_opt_commits, (intptr_t) "HEAD" \
|
||||
}
|
||||
#define OPT_CONTAINS(v, h) _OPT_CONTAINS_OR_WITH("contains", v, h, 0)
|
||||
#define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN)
|
||||
|
||||
#endif
|
||||
|
239
ref-filter.c
239
ref-filter.c
@ -9,6 +9,7 @@
|
||||
#include "tag.h"
|
||||
#include "quote.h"
|
||||
#include "ref-filter.h"
|
||||
#include "revision.h"
|
||||
|
||||
typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
|
||||
|
||||
@ -817,6 +818,114 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
|
||||
*v = &ref->value[atom];
|
||||
}
|
||||
|
||||
enum contains_result {
|
||||
CONTAINS_UNKNOWN = -1,
|
||||
CONTAINS_NO = 0,
|
||||
CONTAINS_YES = 1
|
||||
};
|
||||
|
||||
/*
|
||||
* Mimicking the real stack, this stack lives on the heap, avoiding stack
|
||||
* overflows.
|
||||
*
|
||||
* At each recursion step, the stack items points to the commits whose
|
||||
* ancestors are to be inspected.
|
||||
*/
|
||||
struct contains_stack {
|
||||
int nr, alloc;
|
||||
struct contains_stack_entry {
|
||||
struct commit *commit;
|
||||
struct commit_list *parents;
|
||||
} *contains_stack;
|
||||
};
|
||||
|
||||
static int in_commit_list(const struct commit_list *want, struct commit *c)
|
||||
{
|
||||
for (; want; want = want->next)
|
||||
if (!hashcmp(want->item->object.sha1, c->object.sha1))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test whether the candidate or one of its parents is contained in the list.
|
||||
* Do not recurse to find out, though, but return -1 if inconclusive.
|
||||
*/
|
||||
static enum contains_result contains_test(struct commit *candidate,
|
||||
const struct commit_list *want)
|
||||
{
|
||||
/* was it previously marked as containing a want commit? */
|
||||
if (candidate->object.flags & TMP_MARK)
|
||||
return 1;
|
||||
/* or marked as not possibly containing a want commit? */
|
||||
if (candidate->object.flags & UNINTERESTING)
|
||||
return 0;
|
||||
/* or are we it? */
|
||||
if (in_commit_list(want, candidate)) {
|
||||
candidate->object.flags |= TMP_MARK;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_commit(candidate) < 0)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
|
||||
{
|
||||
ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
|
||||
contains_stack->contains_stack[contains_stack->nr].commit = candidate;
|
||||
contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
|
||||
}
|
||||
|
||||
static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
const struct commit_list *want)
|
||||
{
|
||||
struct contains_stack contains_stack = { 0, 0, NULL };
|
||||
int result = contains_test(candidate, want);
|
||||
|
||||
if (result != CONTAINS_UNKNOWN)
|
||||
return result;
|
||||
|
||||
push_to_contains_stack(candidate, &contains_stack);
|
||||
while (contains_stack.nr) {
|
||||
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
|
||||
struct commit *commit = entry->commit;
|
||||
struct commit_list *parents = entry->parents;
|
||||
|
||||
if (!parents) {
|
||||
commit->object.flags |= UNINTERESTING;
|
||||
contains_stack.nr--;
|
||||
}
|
||||
/*
|
||||
* If we just popped the stack, parents->item has been marked,
|
||||
* therefore contains_test will return a meaningful 0 or 1.
|
||||
*/
|
||||
else switch (contains_test(parents->item, want)) {
|
||||
case CONTAINS_YES:
|
||||
commit->object.flags |= TMP_MARK;
|
||||
contains_stack.nr--;
|
||||
break;
|
||||
case CONTAINS_NO:
|
||||
entry->parents = parents->next;
|
||||
break;
|
||||
case CONTAINS_UNKNOWN:
|
||||
push_to_contains_stack(parents->item, &contains_stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(contains_stack.contains_stack);
|
||||
return contains_test(candidate, want);
|
||||
}
|
||||
|
||||
static int commit_contains(struct ref_filter *filter, struct commit *commit)
|
||||
{
|
||||
if (filter->with_commit_tag_algo)
|
||||
return contains_tag_algo(commit, filter->with_commit);
|
||||
return is_descendant_of(commit, filter->with_commit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return 1 if the refname matches one of the patterns, otherwise 0.
|
||||
* A pattern can be path prefix (e.g. a refname "refs/heads/master"
|
||||
@ -842,6 +951,38 @@ static int match_name_as_path(const char **pattern, const char *refname)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a ref (sha1, refname), check if the ref belongs to the array
|
||||
* of sha1s. If the given ref is a tag, check if the given tag points
|
||||
* at one of the sha1s in the given sha1 array.
|
||||
* the given sha1_array.
|
||||
* NEEDSWORK:
|
||||
* 1. Only a single level of inderection is obtained, we might want to
|
||||
* change this to account for multiple levels (e.g. annotated tags
|
||||
* pointing to annotated tags pointing to a commit.)
|
||||
* 2. As the refs are cached we might know what refname peels to without
|
||||
* the need to parse the object via parse_object(). peel_ref() might be a
|
||||
* more efficient alternative to obtain the pointee.
|
||||
*/
|
||||
static const unsigned char *match_points_at(struct sha1_array *points_at,
|
||||
const unsigned char *sha1,
|
||||
const char *refname)
|
||||
{
|
||||
const unsigned char *tagged_sha1 = NULL;
|
||||
struct object *obj;
|
||||
|
||||
if (sha1_array_lookup(points_at, sha1) >= 0)
|
||||
return sha1;
|
||||
obj = parse_object(sha1);
|
||||
if (!obj)
|
||||
die(_("malformed object at '%s'"), refname);
|
||||
if (obj->type == OBJ_TAG)
|
||||
tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
|
||||
if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
|
||||
return tagged_sha1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate space for a new ref_array_item and copy the objectname and flag to it */
|
||||
static struct ref_array_item *new_ref_array_item(const char *refname,
|
||||
const unsigned char *objectname,
|
||||
@ -866,6 +1007,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
|
||||
struct ref_filter_cbdata *ref_cbdata = cb_data;
|
||||
struct ref_filter *filter = ref_cbdata->filter;
|
||||
struct ref_array_item *ref;
|
||||
struct commit *commit = NULL;
|
||||
|
||||
if (flag & REF_BAD_NAME) {
|
||||
warning("ignoring ref with broken name %s", refname);
|
||||
@ -880,12 +1022,31 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
|
||||
if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
|
||||
return 0;
|
||||
|
||||
if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* A merge filter is applied on refs pointing to commits. Hence
|
||||
* 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) {
|
||||
commit = lookup_commit_reference_gently(oid->hash, 1);
|
||||
if (!commit)
|
||||
return 0;
|
||||
/* We perform the filtering for the '--contains' option */
|
||||
if (filter->with_commit &&
|
||||
!commit_contains(filter, commit))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We do not open the object yet; sort may only need refname
|
||||
* to do its job and the resulting list may yet to be pruned
|
||||
* by maxcount logic.
|
||||
*/
|
||||
ref = new_ref_array_item(refname, oid->hash, flag);
|
||||
ref->commit = commit;
|
||||
|
||||
REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
|
||||
ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
|
||||
@ -911,6 +1072,50 @@ void ref_array_clear(struct ref_array *array)
|
||||
array->nr = array->alloc = 0;
|
||||
}
|
||||
|
||||
static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata)
|
||||
{
|
||||
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);
|
||||
|
||||
init_revisions(&revs, NULL);
|
||||
|
||||
for (i = 0; i < array->nr; i++) {
|
||||
struct ref_array_item *item = array->items[i];
|
||||
add_pending_object(&revs, &item->commit->object, item->refname);
|
||||
to_clear[i] = item->commit;
|
||||
}
|
||||
|
||||
filter->merge_commit->object.flags |= UNINTERESTING;
|
||||
add_pending_object(&revs, &filter->merge_commit->object, "");
|
||||
|
||||
revs.limited = 1;
|
||||
if (prepare_revision_walk(&revs))
|
||||
die(_("revision walk setup failed"));
|
||||
|
||||
old_nr = array->nr;
|
||||
array->nr = 0;
|
||||
|
||||
for (i = 0; i < old_nr; i++) {
|
||||
struct ref_array_item *item = array->items[i];
|
||||
struct commit *commit = item->commit;
|
||||
|
||||
int is_merged = !!(commit->object.flags & UNINTERESTING);
|
||||
|
||||
if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE))
|
||||
array->items[array->nr++] = array->items[i];
|
||||
else
|
||||
free_array_item(item);
|
||||
}
|
||||
|
||||
for (i = 0; i < old_nr; i++)
|
||||
clear_commit_marks(to_clear[i], ALL_REV_FLAGS);
|
||||
clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS);
|
||||
free(to_clear);
|
||||
}
|
||||
|
||||
/*
|
||||
* API for filtering a set of refs. Based on the type of refs the user
|
||||
* has requested, we iterate through those refs and apply filters
|
||||
@ -920,17 +1125,24 @@ void ref_array_clear(struct ref_array *array)
|
||||
int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
|
||||
{
|
||||
struct ref_filter_cbdata ref_cbdata;
|
||||
int ret = 0;
|
||||
|
||||
ref_cbdata.array = array;
|
||||
ref_cbdata.filter = filter;
|
||||
|
||||
/* Simple per-ref filtering */
|
||||
if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
|
||||
return for_each_rawref(ref_filter_handler, &ref_cbdata);
|
||||
ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
|
||||
else if (type & FILTER_REFS_ALL)
|
||||
return for_each_ref(ref_filter_handler, &ref_cbdata);
|
||||
else
|
||||
ret = for_each_ref(ref_filter_handler, &ref_cbdata);
|
||||
else if (type)
|
||||
die("filter_refs: invalid type");
|
||||
return 0;
|
||||
|
||||
/* Filters that need revision walking */
|
||||
if (filter->merge_commit)
|
||||
do_merge_filter(&ref_cbdata);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b)
|
||||
@ -1104,3 +1316,22 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
|
||||
s->atom = parse_ref_filter_atom(arg, arg+len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
struct ref_filter *rf = opt->value;
|
||||
unsigned char sha1[20];
|
||||
|
||||
rf->merge = starts_with(opt->long_name, "no")
|
||||
? REF_FILTER_MERGED_OMIT
|
||||
: REF_FILTER_MERGED_INCLUDE;
|
||||
|
||||
if (get_sha1(arg, sha1))
|
||||
die(_("malformed object name %s"), arg);
|
||||
|
||||
rf->merge_commit = lookup_commit_reference_gently(sha1, 0);
|
||||
if (!rf->merge_commit)
|
||||
return opterror(opt, "must point to a commit", 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
23
ref-filter.h
23
ref-filter.h
@ -31,6 +31,7 @@ struct ref_array_item {
|
||||
unsigned char objectname[20];
|
||||
int flag;
|
||||
const char *symref;
|
||||
struct commit *commit;
|
||||
struct atom_value *value;
|
||||
char refname[FLEX_ARRAY];
|
||||
};
|
||||
@ -42,6 +43,17 @@ struct ref_array {
|
||||
|
||||
struct ref_filter {
|
||||
const char **name_patterns;
|
||||
struct sha1_array points_at;
|
||||
struct commit_list *with_commit;
|
||||
|
||||
enum {
|
||||
REF_FILTER_MERGED_NONE = 0,
|
||||
REF_FILTER_MERGED_INCLUDE,
|
||||
REF_FILTER_MERGED_OMIT
|
||||
} merge;
|
||||
struct commit *merge_commit;
|
||||
|
||||
unsigned int with_commit_tag_algo : 1;
|
||||
};
|
||||
|
||||
struct ref_filter_cbdata {
|
||||
@ -49,6 +61,15 @@ struct ref_filter_cbdata {
|
||||
struct ref_filter *filter;
|
||||
};
|
||||
|
||||
/* Macros for checking --merged and --no-merged options */
|
||||
#define _OPT_MERGED_NO_MERGED(option, filter, h) \
|
||||
{ OPTION_CALLBACK, 0, option, (filter), N_("commit"), (h), \
|
||||
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, \
|
||||
parse_opt_merge_filter, (intptr_t) "HEAD" \
|
||||
}
|
||||
#define OPT_MERGED(f, h) _OPT_MERGED_NO_MERGED("merged", f, h)
|
||||
#define OPT_NO_MERGED(f, h) _OPT_MERGED_NO_MERGED("no-merged", f, h)
|
||||
|
||||
/*
|
||||
* API for filtering a set of refs. Based on the type of refs the user
|
||||
* has requested, we iterate through those refs and apply filters
|
||||
@ -70,5 +91,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
|
||||
int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
|
||||
/* Default sort option based on refname */
|
||||
struct ref_sorting *ref_default_sorting(void);
|
||||
/* Function to parse --merged and --no-merged options */
|
||||
int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset);
|
||||
|
||||
#endif /* REF_FILTER_H */
|
||||
|
84
t/t6302-for-each-ref-filter.sh
Executable file
84
t/t6302-for-each-ref-filter.sh
Executable file
@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='test for-each-refs usage of ref-filter APIs'
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/lib-gpg.sh
|
||||
|
||||
if ! test_have_prereq GPG
|
||||
then
|
||||
skip_all="skipping for-each-ref tests, GPG not available"
|
||||
test_done
|
||||
fi
|
||||
|
||||
test_expect_success 'setup some history and refs' '
|
||||
test_commit one &&
|
||||
test_commit two &&
|
||||
test_commit three &&
|
||||
git checkout -b side &&
|
||||
test_commit four &&
|
||||
git tag -s -m "A signed tag message" signed-tag &&
|
||||
git tag -s -m "Annonated doubly" double-tag signed-tag &&
|
||||
git checkout master &&
|
||||
git update-ref refs/odd/spot master
|
||||
'
|
||||
|
||||
test_expect_success 'filtering with --points-at' '
|
||||
cat >expect <<-\EOF &&
|
||||
refs/heads/master
|
||||
refs/odd/spot
|
||||
refs/tags/three
|
||||
EOF
|
||||
git for-each-ref --format="%(refname)" --points-at=master >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'check signed tags with --points-at' '
|
||||
sed -e "s/Z$//" >expect <<-\EOF &&
|
||||
refs/heads/side Z
|
||||
refs/tags/four Z
|
||||
refs/tags/signed-tag four
|
||||
EOF
|
||||
git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'filtering with --merged' '
|
||||
cat >expect <<-\EOF &&
|
||||
refs/heads/master
|
||||
refs/odd/spot
|
||||
refs/tags/one
|
||||
refs/tags/three
|
||||
refs/tags/two
|
||||
EOF
|
||||
git for-each-ref --format="%(refname)" --merged=master >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'filtering with --no-merged' '
|
||||
cat >expect <<-\EOF &&
|
||||
refs/heads/side
|
||||
refs/tags/double-tag
|
||||
refs/tags/four
|
||||
refs/tags/signed-tag
|
||||
EOF
|
||||
git for-each-ref --format="%(refname)" --no-merged=master >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'filtering with --contains' '
|
||||
cat >expect <<-\EOF &&
|
||||
refs/heads/master
|
||||
refs/heads/side
|
||||
refs/odd/spot
|
||||
refs/tags/double-tag
|
||||
refs/tags/four
|
||||
refs/tags/signed-tag
|
||||
refs/tags/three
|
||||
refs/tags/two
|
||||
EOF
|
||||
git for-each-ref --format="%(refname)" --contains=two >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user