]> git.ipfire.org Git - thirdparty/git.git/blobdiff - ref-filter.c
Merge branch 'vd/fsck-submodule-url-test'
[thirdparty/git.git] / ref-filter.c
index 3de4cefcf5cab8d6456cc3b5910555a17754dfef..35b989e1dfe59e9d274afead3d397f97ed624b26 100644 (file)
@@ -13,6 +13,8 @@
 #include "oid-array.h"
 #include "repository.h"
 #include "commit.h"
+#include "mailmap.h"
+#include "ident.h"
 #include "remote.h"
 #include "color.h"
 #include "tag.h"
 #include "ref-filter.h"
 #include "revision.h"
 #include "utf8.h"
-#include "version.h"
 #include "versioncmp.h"
 #include "trailer.h"
 #include "wt-status.h"
 #include "commit-slab.h"
-#include "commit-graph.h"
 #include "commit-reach.h"
 #include "worktree.h"
 #include "hashmap.h"
-#include "strvec.h"
 
 static struct ref_msg {
        const char *gone;
@@ -215,8 +214,16 @@ static struct used_atom {
                struct {
                        enum { O_SIZE, O_SIZE_DISK } option;
                } objectsize;
-               struct email_option {
-                       enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
+               struct {
+                       enum { N_RAW, N_MAILMAP } option;
+               } name_option;
+               struct {
+                       enum {
+                               EO_RAW = 0,
+                               EO_TRIM = 1<<0,
+                               EO_LOCALPART = 1<<1,
+                               EO_MAILMAP = 1<<2,
+                       } option;
                } email_option;
                struct {
                        enum { S_BARE, S_GRADE, S_SIGNER, S_KEY,
@@ -549,7 +556,8 @@ static int signature_atom_parser(struct ref_format *format UNUSED,
        return 0;
 }
 
-static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom,
+static int trailers_atom_parser(struct ref_format *format UNUSED,
+                               struct used_atom *atom,
                                const char *arg, struct strbuf *err)
 {
        atom->u.contents.trailer_opts.no_divider = 1;
@@ -719,21 +727,55 @@ static int oid_atom_parser(struct ref_format *format UNUSED,
        return 0;
 }
 
-static int person_email_atom_parser(struct ref_format *format UNUSED,
-                                   struct used_atom *atom,
-                                   const char *arg, struct strbuf *err)
+static int person_name_atom_parser(struct ref_format *format UNUSED,
+                                  struct used_atom *atom,
+                                  const char *arg, struct strbuf *err)
 {
        if (!arg)
-               atom->u.email_option.option = EO_RAW;
-       else if (!strcmp(arg, "trim"))
-               atom->u.email_option.option = EO_TRIM;
-       else if (!strcmp(arg, "localpart"))
-               atom->u.email_option.option = EO_LOCALPART;
+               atom->u.name_option.option = N_RAW;
+       else if (!strcmp(arg, "mailmap"))
+               atom->u.name_option.option = N_MAILMAP;
        else
                return err_bad_arg(err, atom->name, arg);
        return 0;
 }
 
+static int email_atom_option_parser(struct used_atom *atom,
+                                   const char **arg, struct strbuf *err)
+{
+       if (!*arg)
+               return EO_RAW;
+       if (skip_prefix(*arg, "trim", arg))
+               return EO_TRIM;
+       if (skip_prefix(*arg, "localpart", arg))
+               return EO_LOCALPART;
+       if (skip_prefix(*arg, "mailmap", arg))
+               return EO_MAILMAP;
+       return -1;
+}
+
+static int person_email_atom_parser(struct ref_format *format UNUSED,
+                                   struct used_atom *atom,
+                                   const char *arg, struct strbuf *err)
+{
+       for (;;) {
+               int opt = email_atom_option_parser(atom, &arg, err);
+               const char *bad_arg = arg;
+
+               if (opt < 0)
+                       return err_bad_arg(err, atom->name, bad_arg);
+               atom->u.email_option.option |= opt;
+
+               if (!arg || !*arg)
+                       break;
+               if (*arg == ',')
+                       arg++;
+               else
+                       return err_bad_arg(err, atom->name, bad_arg);
+       }
+       return 0;
+}
+
 static int refname_atom_parser(struct ref_format *format UNUSED,
                               struct used_atom *atom,
                               const char *arg, struct strbuf *err)
@@ -821,7 +863,7 @@ static int if_atom_parser(struct ref_format *format UNUSED,
        return 0;
 }
 
-static int rest_atom_parser(struct ref_format *format,
+static int rest_atom_parser(struct ref_format *format UNUSED,
                            struct used_atom *atom UNUSED,
                            const char *arg, struct strbuf *err)
 {
@@ -830,7 +872,8 @@ static int rest_atom_parser(struct ref_format *format,
        return 0;
 }
 
-static int ahead_behind_atom_parser(struct ref_format *format, struct used_atom *atom,
+static int ahead_behind_atom_parser(struct ref_format *format,
+                                   struct used_atom *atom UNUSED,
                                    const char *arg, struct strbuf *err)
 {
        struct string_list_item *item;
@@ -875,15 +918,15 @@ static struct {
        [ATOM_TYPE] = { "type", SOURCE_OBJ },
        [ATOM_TAG] = { "tag", SOURCE_OBJ },
        [ATOM_AUTHOR] = { "author", SOURCE_OBJ },
-       [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ },
+       [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
        [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME },
        [ATOM_COMMITTER] = { "committer", SOURCE_OBJ },
-       [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ },
+       [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
        [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME },
        [ATOM_TAGGER] = { "tagger", SOURCE_OBJ },
-       [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ },
+       [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser },
        [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME },
        [ATOM_CREATOR] = { "creator", SOURCE_OBJ },
@@ -1484,32 +1527,49 @@ static const char *copy_name(const char *buf)
        return xstrdup("");
 }
 
+static const char *find_end_of_email(const char *email, int opt)
+{
+       const char *eoemail;
+
+       if (opt & EO_LOCALPART) {
+               eoemail = strchr(email, '@');
+               if (eoemail)
+                       return eoemail;
+               return strchr(email, '>');
+       }
+
+       if (opt & EO_TRIM)
+               return strchr(email, '>');
+
+       /*
+        * The option here is either the raw email option or the raw
+        * mailmap option (that is EO_RAW or EO_MAILMAP). In such cases,
+        * we directly grab the whole email including the closing
+        * angle brackets.
+        *
+        * If EO_MAILMAP was set with any other option (that is either
+        * EO_TRIM or EO_LOCALPART), we already grab the end of email
+        * above.
+        */
+       eoemail = strchr(email, '>');
+       if (eoemail)
+               eoemail++;
+       return eoemail;
+}
+
 static const char *copy_email(const char *buf, struct used_atom *atom)
 {
        const char *email = strchr(buf, '<');
        const char *eoemail;
+       int opt = atom->u.email_option.option;
+
        if (!email)
                return xstrdup("");
-       switch (atom->u.email_option.option) {
-       case EO_RAW:
-               eoemail = strchr(email, '>');
-               if (eoemail)
-                       eoemail++;
-               break;
-       case EO_TRIM:
-               email++;
-               eoemail = strchr(email, '>');
-               break;
-       case EO_LOCALPART:
+
+       if (opt & (EO_LOCALPART | EO_TRIM))
                email++;
-               eoemail = strchr(email, '@');
-               if (!eoemail)
-                       eoemail = strchr(email, '>');
-               break;
-       default:
-               BUG("unknown email option");
-       }
 
+       eoemail = find_end_of_email(email, opt);
        if (!eoemail)
                return xstrdup("");
        return xmemdupz(email, eoemail - email);
@@ -1570,16 +1630,23 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam
        v->value = 0;
 }
 
+static struct string_list mailmap = STRING_LIST_INIT_NODUP;
+
 /* See grab_values */
 static void grab_person(const char *who, struct atom_value *val, int deref, void *buf)
 {
        int i;
        int wholen = strlen(who);
        const char *wholine = NULL;
+       const char *headers[] = { "author ", "committer ",
+                                 "tagger ", NULL };
 
        for (i = 0; i < used_atom_cnt; i++) {
-               const char *name = used_atom[i].name;
+               struct used_atom *atom = &used_atom[i];
+               const char *name = atom->name;
                struct atom_value *v = &val[i];
+               struct strbuf mailmap_buf = STRBUF_INIT;
+
                if (!!deref != (*name == '*'))
                        continue;
                if (deref)
@@ -1587,22 +1654,36 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
                if (strncmp(who, name, wholen))
                        continue;
                if (name[wholen] != 0 &&
-                   strcmp(name + wholen, "name") &&
+                   !starts_with(name + wholen, "name") &&
                    !starts_with(name + wholen, "email") &&
                    !starts_with(name + wholen, "date"))
                        continue;
-               if (!wholine)
+
+               if ((starts_with(name + wholen, "name") &&
+                   (atom->u.name_option.option == N_MAILMAP)) ||
+                   (starts_with(name + wholen, "email") &&
+                   (atom->u.email_option.option & EO_MAILMAP))) {
+                       if (!mailmap.items)
+                               read_mailmap(&mailmap);
+                       strbuf_addstr(&mailmap_buf, buf);
+                       apply_mailmap_to_header(&mailmap_buf, headers, &mailmap);
+                       wholine = find_wholine(who, wholen, mailmap_buf.buf);
+               } else {
                        wholine = find_wholine(who, wholen, buf);
+               }
+
                if (!wholine)
                        return; /* no point looking for it */
                if (name[wholen] == 0)
                        v->s = copy_line(wholine);
-               else if (!strcmp(name + wholen, "name"))
+               else if (starts_with(name + wholen, "name"))
                        v->s = copy_name(wholine);
                else if (starts_with(name + wholen, "email"))
                        v->s = copy_email(wholine, &used_atom[i]);
                else if (starts_with(name + wholen, "date"))
                        grab_date(wholine, v, name);
+
+               strbuf_release(&mailmap_buf);
        }
 
        /*
@@ -2128,7 +2209,7 @@ char *get_head_description(void)
                                    state.detached_from);
        } else if (state.bisect_in_progress)
                strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
-                           state.branch);
+                           state.bisecting_from);
        else if (state.detached_from) {
                if (state.detached_at)
                        strbuf_addf(&desc, _("(HEAD detached at %s)"),
@@ -2424,17 +2505,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
                return 0;
 
        /*
-        * If it is a tag object, see if we use a value that derefs
-        * the object, and if we do grab the object it refers to.
+        * If it is a tag object, see if we use the peeled value. If we do,
+        * grab the peeled OID.
         */
-       oi_deref.oid = *get_tagged_oid((struct tag *)obj);
+       if (need_tagged && peel_iterated_oid(&obj->oid, &oi_deref.oid))
+               die("bad tag");
 
-       /*
-        * NEEDSWORK: This derefs tag only once, which
-        * is good to deal with chains of trust, but
-        * is not consistent with what deref_tag() does
-        * which peels the onion to the core.
-        */
        return get_object(ref, 1, &obj, &oi_deref, err);
 }
 
@@ -2632,15 +2708,18 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
        return ref;
 }
 
+static void ref_array_append(struct ref_array *array, struct ref_array_item *ref)
+{
+       ALLOC_GROW(array->items, array->nr + 1, array->alloc);
+       array->items[array->nr++] = ref;
+}
+
 struct ref_array_item *ref_array_push(struct ref_array *array,
                                      const char *refname,
                                      const struct object_id *oid)
 {
        struct ref_array_item *ref = new_ref_array_item(refname, oid);
-
-       ALLOC_GROW(array->items, array->nr + 1, array->alloc);
-       array->items[array->nr++] = ref;
-
+       ref_array_append(array, ref);
        return ref;
 }
 
@@ -2677,46 +2756,36 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
        return ref_kind_from_refname(refname);
 }
 
-struct ref_filter_cbdata {
-       struct ref_array *array;
-       struct ref_filter *filter;
-};
-
-/*
- * A call-back given to for_each_ref().  Filter refs and keep them for
- * later object processing.
- */
-static int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data)
+static struct ref_array_item *apply_ref_filter(const char *refname, const struct object_id *oid,
+                           int flag, struct ref_filter *filter)
 {
-       struct ref_filter_cbdata *ref_cbdata = cb_data;
-       struct ref_filter *filter = ref_cbdata->filter;
        struct ref_array_item *ref;
        struct commit *commit = NULL;
        unsigned int kind;
 
        if (flag & REF_BAD_NAME) {
                warning(_("ignoring ref with broken name %s"), refname);
-               return 0;
+               return NULL;
        }
 
        if (flag & REF_ISBROKEN) {
                warning(_("ignoring broken ref %s"), refname);
-               return 0;
+               return NULL;
        }
 
        /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
        kind = filter_ref_kind(filter, refname);
        if (!(kind & filter->kind))
-               return 0;
+               return NULL;
 
        if (!filter_pattern_match(filter, refname))
-               return 0;
+               return NULL;
 
        if (filter_exclude_match(filter, refname))
-               return 0;
+               return NULL;
 
        if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))
-               return 0;
+               return NULL;
 
        /*
         * A merge filter is applied on refs pointing to commits. Hence
@@ -2727,15 +2796,15 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
            filter->with_commit || filter->no_commit || filter->verbose) {
                commit = lookup_commit_reference_gently(the_repository, oid, 1);
                if (!commit)
-                       return 0;
+                       return NULL;
                /* We perform the filtering for the '--contains' option... */
                if (filter->with_commit &&
                    !commit_contains(filter, commit, filter->with_commit, &filter->internal.contains_cache))
-                       return 0;
+                       return NULL;
                /* ...or for the `--no-contains' option */
                if (filter->no_commit &&
                    commit_contains(filter, commit, filter->no_commit, &filter->internal.no_contains_cache))
-                       return 0;
+                       return NULL;
        }
 
        /*
@@ -2743,11 +2812,32 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
         * to do its job and the resulting list may yet to be pruned
         * by maxcount logic.
         */
-       ref = ref_array_push(ref_cbdata->array, refname, oid);
+       ref = new_ref_array_item(refname, oid);
        ref->commit = commit;
        ref->flag = flag;
        ref->kind = kind;
 
+       return ref;
+}
+
+struct ref_filter_cbdata {
+       struct ref_array *array;
+       struct ref_filter *filter;
+};
+
+/*
+ * A call-back given to for_each_ref().  Filter refs and keep them for
+ * later object processing.
+ */
+static int filter_one(const char *refname, const struct object_id *oid, int flag, void *cb_data)
+{
+       struct ref_filter_cbdata *ref_cbdata = cb_data;
+       struct ref_array_item *ref;
+
+       ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter);
+       if (ref)
+               ref_array_append(ref_cbdata->array, ref);
+
        return 0;
 }
 
@@ -2765,6 +2855,49 @@ static void free_array_item(struct ref_array_item *item)
        free(item);
 }
 
+struct ref_filter_and_format_cbdata {
+       struct ref_filter *filter;
+       struct ref_format *format;
+
+       struct ref_filter_and_format_internal {
+               int count;
+       } internal;
+};
+
+static int filter_and_format_one(const char *refname, const struct object_id *oid, int flag, void *cb_data)
+{
+       struct ref_filter_and_format_cbdata *ref_cbdata = cb_data;
+       struct ref_array_item *ref;
+       struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
+
+       ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter);
+       if (!ref)
+               return 0;
+
+       if (format_ref_array_item(ref, ref_cbdata->format, &output, &err))
+               die("%s", err.buf);
+
+       if (output.len || !ref_cbdata->format->array_opts.omit_empty) {
+               fwrite(output.buf, 1, output.len, stdout);
+               putchar('\n');
+       }
+
+       strbuf_release(&output);
+       strbuf_release(&err);
+       free_array_item(ref);
+
+       /*
+        * Increment the running count of refs that match the filter. If
+        * max_count is set and we've reached the max, stop the ref
+        * iteration by returning a nonzero value.
+        */
+       if (ref_cbdata->format->array_opts.max_count &&
+           ++ref_cbdata->internal.count >= ref_cbdata->format->array_opts.max_count)
+               return 1;
+
+       return 0;
+}
+
 /* Free all memory allocated for ref_array */
 void ref_array_clear(struct ref_array *array)
 {
@@ -2883,26 +3016,12 @@ void filter_ahead_behind(struct repository *r,
        free(commits);
 }
 
-/*
- * 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
- * as per the given ref_filter structure and finally store the
- * filtered refs in the ref_array structure.
- */
-int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
+static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data)
 {
-       struct ref_filter_cbdata ref_cbdata;
-       int save_commit_buffer_orig;
        int ret = 0;
 
-       ref_cbdata.array = array;
-       ref_cbdata.filter = filter;
-
        filter->kind = type & FILTER_REFS_KIND_MASK;
 
-       save_commit_buffer_orig = save_commit_buffer;
-       save_commit_buffer = 0;
-
        init_contains_cache(&filter->internal.contains_cache);
        init_contains_cache(&filter->internal.no_contains_cache);
 
@@ -2917,20 +3036,43 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
                 * of filter_ref_kind().
                 */
                if (filter->kind == FILTER_REFS_BRANCHES)
-                       ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata);
+                       ret = for_each_fullref_in("refs/heads/", fn, cb_data);
                else if (filter->kind == FILTER_REFS_REMOTES)
-                       ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata);
+                       ret = for_each_fullref_in("refs/remotes/", fn, cb_data);
                else if (filter->kind == FILTER_REFS_TAGS)
-                       ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata);
+                       ret = for_each_fullref_in("refs/tags/", fn, cb_data);
                else if (filter->kind & FILTER_REFS_ALL)
-                       ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata);
+                       ret = for_each_fullref_in_pattern(filter, fn, cb_data);
                if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
-                       head_ref(ref_filter_handler, &ref_cbdata);
+                       head_ref(fn, cb_data);
        }
 
        clear_contains_cache(&filter->internal.contains_cache);
        clear_contains_cache(&filter->internal.no_contains_cache);
 
+       return ret;
+}
+
+/*
+ * 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
+ * as per the given ref_filter structure and finally store the
+ * filtered refs in the ref_array structure.
+ */
+int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type)
+{
+       struct ref_filter_cbdata ref_cbdata;
+       int save_commit_buffer_orig;
+       int ret = 0;
+
+       ref_cbdata.array = array;
+       ref_cbdata.filter = filter;
+
+       save_commit_buffer_orig = save_commit_buffer;
+       save_commit_buffer = 0;
+
+       ret = do_filter_refs(filter, type, filter_one, &ref_cbdata);
+
        /*  Filters that need revision walking */
        reach_filter(array, &filter->reachable_from, INCLUDE_REACHED);
        reach_filter(array, &filter->unreachable_from, EXCLUDE_REACHED);
@@ -2939,6 +3081,51 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
        return ret;
 }
 
+static inline int can_do_iterative_format(struct ref_filter *filter,
+                                         struct ref_sorting *sorting,
+                                         struct ref_format *format)
+{
+       /*
+        * Filtering & formatting results within a single ref iteration
+        * callback is not compatible with options that require
+        * post-processing a filtered ref_array. These include:
+        * - filtering on reachability
+        * - sorting the filtered results
+        * - including ahead-behind information in the formatted output
+        */
+       return !(filter->reachable_from ||
+                filter->unreachable_from ||
+                sorting ||
+                format->bases.nr);
+}
+
+void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
+                           struct ref_sorting *sorting,
+                           struct ref_format *format)
+{
+       if (can_do_iterative_format(filter, sorting, format)) {
+               int save_commit_buffer_orig;
+               struct ref_filter_and_format_cbdata ref_cbdata = {
+                       .filter = filter,
+                       .format = format,
+               };
+
+               save_commit_buffer_orig = save_commit_buffer;
+               save_commit_buffer = 0;
+
+               do_filter_refs(filter, type, filter_and_format_one, &ref_cbdata);
+
+               save_commit_buffer = save_commit_buffer_orig;
+       } else {
+               struct ref_array array = { 0 };
+               filter_refs(&array, filter, type);
+               filter_ahead_behind(the_repository, format, &array);
+               ref_array_sort(sorting, &array);
+               print_formatted_ref_array(&array, format);
+               ref_array_clear(&array);
+       }
+}
+
 static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)
 {
        if (!(a->kind ^ b->kind))
@@ -3128,6 +3315,29 @@ int format_ref_array_item(struct ref_array_item *info,
        return 0;
 }
 
+void print_formatted_ref_array(struct ref_array *array, struct ref_format *format)
+{
+       int total;
+       struct strbuf output = STRBUF_INIT, err = STRBUF_INIT;
+
+       total = format->array_opts.max_count;
+       if (!total || array->nr < total)
+               total = array->nr;
+       for (int i = 0; i < total; i++) {
+               strbuf_reset(&err);
+               strbuf_reset(&output);
+               if (format_ref_array_item(array->items[i], format, &output, &err))
+                       die("%s", err.buf);
+               if (output.len || !format->array_opts.omit_empty) {
+                       fwrite(output.buf, 1, output.len, stdout);
+                       putchar('\n');
+               }
+       }
+
+       strbuf_release(&err);
+       strbuf_release(&output);
+}
+
 void pretty_print_ref(const char *name, const struct object_id *oid,
                      struct ref_format *format)
 {