@ -9,6 +9,7 @@
@@ -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
@@ -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)
@@ -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,
@@ -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,
@@ -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)
@@ -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)
@@ -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)
@@ -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;
}