#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;
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,
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;
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)
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)
{
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;
[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 },
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);
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)
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);
}
/*
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)"),
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);
}
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;
}
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
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;
}
/*
* 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;
}
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)
{
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);
* 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);
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))
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)
{