#include "gpg-interface.h"
static const char * const fmt_merge_msg_usage[] = {
- "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
+ N_("git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"),
NULL
};
merge_log_config = DEFAULT_MERGE_LOG_LEN;
} else if (!strcmp(key, "merge.branchdesc")) {
use_branch_desc = git_config_bool(key, value);
+ } else {
+ return git_default_config(key, value, cb);
}
return 0;
}
static struct string_list srcs = STRING_LIST_INIT_DUP;
static struct string_list origins = STRING_LIST_INIT_DUP;
-static int handle_line(char *line)
+struct merge_parents {
+ int alloc, nr;
+ struct merge_parent {
+ unsigned char given[20];
+ unsigned char commit[20];
+ unsigned char used;
+ } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ int i;
+ for (i = 0; i < table->nr; i++) {
+ if (given && hashcmp(table->item[i].given, given))
+ continue;
+ if (commit && hashcmp(table->item[i].commit, commit))
+ continue;
+ return &table->item[i];
+ }
+ return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ if (table->nr && find_merge_parent(table, given, commit))
+ return;
+ ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+ hashcpy(table->item[table->nr].given, given);
+ hashcpy(table->item[table->nr].commit, commit);
+ table->item[table->nr].used = 0;
+ table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
{
int i, len = strlen(line);
struct origin_data *origin_data;
struct src_data *src_data;
struct string_list_item *item;
int pulling_head = 0;
+ unsigned char sha1[20];
if (len < 43 || line[40] != '\t')
return 1;
if (line[41] != '\t')
return 2;
- line[40] = 0;
- origin_data = xcalloc(1, sizeof(struct origin_data));
- i = get_sha1(line, origin_data->sha1);
- line[40] = '\t';
- if (i) {
- free(origin_data);
+ i = get_sha1_hex(line, sha1);
+ if (i)
return 3;
- }
+
+ if (!find_merge_parent(merge_parents, sha1, NULL))
+ return 0; /* subsumed by other parents */
+
+ origin_data = xcalloc(1, sizeof(struct origin_data));
+ hashcpy(origin_data->sha1, sha1);
if (line[len - 1] == '\n')
line[len - 1] = 0;
strbuf_release(&desc);
}
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ char *name_buf, *name, *name_end;
+ struct string_list_item *elem;
+ const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+
+ name = strstr(commit->buffer, field);
+ if (!name)
+ return;
+ name += strlen(field);
+ name_end = strchrnul(name, '<');
+ if (*name_end)
+ name_end--;
+ while (isspace(*name_end) && name <= name_end)
+ name_end--;
+ if (name_end < name)
+ return;
+ name_buf = xmemdupz(name, name_end - name + 1);
+
+ elem = string_list_lookup(people, name_buf);
+ if (!elem) {
+ elem = string_list_insert(people, name_buf);
+ elem->util = (void *)0;
+ }
+ elem->util = (void*)(util_as_integral(elem) + 1);
+ free(name_buf);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_, *b = b_;
+ return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+ if (people->nr == 1)
+ strbuf_addf(out, "%s", people->items[0].string);
+ else if (people->nr == 2)
+ strbuf_addf(out, "%s (%d) and %s (%d)",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]),
+ people->items[1].string,
+ (int)util_as_integral(&people->items[1]));
+ else if (people->nr)
+ strbuf_addf(out, "%s (%d) and others",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+ struct string_list *them,
+ int kind)
+{
+ const char *label;
+ const char *me;
+
+ if (kind == 'a') {
+ label = "\n# By ";
+ me = git_author_info(IDENT_NO_DATE);
+ } else {
+ label = "\n# Via ";
+ me = git_committer_info(IDENT_NO_DATE);
+ }
+
+ if (!them->nr ||
+ (them->nr == 1 &&
+ me &&
+ (me = skip_prefix(me, them->items->string)) != NULL &&
+ skip_prefix(me, " <")))
+ return;
+ strbuf_addstr(out, label);
+ add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+ struct string_list *authors,
+ struct string_list *committers)
+{
+ if (authors->nr)
+ qsort(authors->items,
+ authors->nr, sizeof(authors->items[0]),
+ cmp_string_list_util_as_integral);
+ if (committers->nr)
+ qsort(committers->items,
+ committers->nr, sizeof(committers->items[0]),
+ cmp_string_list_util_as_integral);
+
+ credit_people(out, authors, 'a');
+ credit_people(out, committers, 'c');
+}
+
static void shortlog(const char *name,
struct origin_data *origin_data,
struct commit *head,
struct commit *commit;
struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP;
+ struct string_list authors = STRING_LIST_INIT_DUP;
+ struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
const unsigned char *sha1 = origin_data->sha1;
return;
setup_revisions(0, NULL, rev, NULL);
- rev->ignore_merges = 1;
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0};
- /* ignore merges */
- if (commit->parents && commit->parents->next)
+ if (commit->parents && commit->parents->next) {
+ /* do not list a merge but count committer */
+ record_person('c', &committers, commit);
continue;
-
+ }
+ if (!count)
+ /* the 'tip' committer */
+ record_person('c', &committers, commit);
+ record_person('a', &authors, commit);
count++;
if (subjects.nr > limit)
continue;
string_list_append(&subjects, strbuf_detach(&sb, NULL));
}
+ add_people_info(out, &authors, &committers);
if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
rev->commits = NULL;
rev->pending.nr = 0;
+ string_list_clear(&authors, 0);
+ string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}
strbuf_add(tagbuf, tag_body, buf + len - tag_body);
}
strbuf_complete_line(tagbuf);
- strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+ if (sig->len) {
+ strbuf_addch(tagbuf, '\n');
+ strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len);
+ }
}
static void fmt_merge_msg_sigs(struct strbuf *out)
strbuf_release(&tagbuf);
}
+static void find_merge_parents(struct merge_parents *result,
+ struct strbuf *in, unsigned char *head)
+{
+ struct commit_list *parents, *next;
+ struct commit *head_commit;
+ int pos = 0, i, j;
+
+ parents = NULL;
+ while (pos < in->len) {
+ int len;
+ char *p = in->buf + pos;
+ char *newline = strchr(p, '\n');
+ unsigned char sha1[20];
+ struct commit *parent;
+ struct object *obj;
+
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+
+ if (len < 43 ||
+ get_sha1_hex(p, sha1) ||
+ p[40] != '\t' ||
+ p[41] != '\t')
+ continue; /* skip not-for-merge */
+ /*
+ * Do not use get_merge_parent() here; we do not have
+ * "name" here and we do not want to contaminate its
+ * util field yet.
+ */
+ obj = parse_object(sha1);
+ parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+ if (!parent)
+ continue;
+ commit_list_insert(parent, &parents);
+ add_merge_parent(result, obj->sha1, parent->object.sha1);
+ }
+ head_commit = lookup_commit(head);
+ if (head_commit)
+ commit_list_insert(head_commit, &parents);
+ parents = reduce_heads(parents);
+
+ while (parents) {
+ for (i = 0; i < result->nr; i++)
+ if (!hashcmp(result->item[i].commit,
+ parents->item->object.sha1))
+ result->item[i].used = 1;
+ next = parents->next;
+ free(parents);
+ parents = next;
+ }
+
+ for (i = j = 0; i < result->nr; i++) {
+ if (result->item[i].used) {
+ if (i != j)
+ result->item[j] = result->item[i];
+ j++;
+ }
+ }
+ result->nr = j;
+}
+
int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
struct fmt_merge_msg_opts *opts)
{
unsigned char head_sha1[20];
const char *current_branch;
void *current_branch_to_free;
+ struct merge_parents merge_parents;
+
+ memset(&merge_parents, 0, sizeof(merge_parents));
/* get current branch */
current_branch = current_branch_to_free =
if (!prefixcmp(current_branch, "refs/heads/"))
current_branch += 11;
+ find_merge_parents(&merge_parents, in, head_sha1);
+
/* get a line */
while (pos < in->len) {
int len;
pos += len + !!newline;
i++;
p[len] = 0;
- if (handle_line(p))
+ if (handle_line(p, &merge_parents))
die ("Error in line %d: %.*s", i, len, p);
}
rev.ignore_merges = 1;
rev.limited = 1;
- if (suffixcmp(out->buf, "\n"))
- strbuf_addch(out, '\n');
+ strbuf_complete_line(out);
for (i = 0; i < origins.nr; i++)
shortlog(origins.items[i].string,
strbuf_complete_line(out);
free(current_branch_to_free);
+ free(merge_parents.item);
return 0;
}
const char *message = NULL;
int shortlog_len = -1;
struct option options[] = {
- { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
- "populate log with at most <n> entries from shortlog",
+ { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"),
+ N_("populate log with at most <n> entries from shortlog"),
PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
- { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
- "alias for --log (deprecated)",
+ { OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"),
+ N_("alias for --log (deprecated)"),
PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
DEFAULT_MERGE_LOG_LEN },
- OPT_STRING('m', "message", &message, "text",
- "use <text> as start of message"),
- OPT_FILENAME('F', "file", &inpath, "file to read from"),
+ OPT_STRING('m', "message", &message, N_("text"),
+ N_("use <text> as start of message")),
+ OPT_FILENAME('F', "file", &inpath, N_("file to read from")),
OPT_END()
};