Skip to content

Commit

Permalink
fmt-merge-msg: show those involved in a merged series
Browse files Browse the repository at this point in the history
As we already walk the history of the branch that gets merged to
come up with a short log, let's label it with names of the primary
authors, so that the user who summarizes the merge can easily give
credit to them in the log message.

Also infer the names of "lieutents" to help integrators at higher
level of the food-chain to give credit to them, by counting:

 * The committer of the 'tip' commit that is merged
 * The committer of merge commits that are merged

Often the first one gives the owner of the history being pulled, but
his last pull from his sublieutenants may have been a fast-forward,
in which case the first one would not be.  The latter rule will
count the integrator of the history, so together it might be a
reasonable heuristics.

There are two special cases:

 - The "author" credit is omitted when the series is written solely
   by the same author who is making the merge. The name can be seen
   on the "Author" line of the "git log" output to view the log
   message anyway.

 - The "lieutenant" credit is omitted when there is only one key
   committer in the merged branch and it is the committer who is
   making the merge. Typically this applies to the case where the
   developer merges his own branch.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Junio C Hamano committed Mar 14, 2012
1 parent ead8eb8 commit 418a143
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 7 deletions.
114 changes: 110 additions & 4 deletions builtin/fmt-merge-msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
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;
}
Expand Down Expand Up @@ -180,6 +182,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
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[MAX_GITNAME], *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 || name + MAX_GITNAME <= name_end)
return;
memcpy(name_buf, name, name_end - name + 1);
name_buf[name_end - name + 1] = '\0';

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);
}

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 = "\nBy ";
me = git_author_info(IDENT_NO_DATE);
} else {
label = "\nvia ";
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,
Expand All @@ -190,6 +287,8 @@ static void shortlog(const char *name,
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;
Expand All @@ -199,7 +298,6 @@ static void shortlog(const char *name,
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;
Expand All @@ -208,10 +306,15 @@ static void shortlog(const char *name,
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;
Expand All @@ -226,6 +329,7 @@ static void shortlog(const char *name,
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
Expand All @@ -246,6 +350,8 @@ static void shortlog(const char *name,
rev->commits = NULL;
rev->pending.nr = 0;

string_list_clear(&authors, 0);
string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}

Expand Down
27 changes: 24 additions & 3 deletions t/t6200-fmt-merge-msg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ test_expect_success setup '
echo "l3" >two &&
test_tick &&
git commit -a -m "Left #3" &&
GIT_COMMITTER_NAME="Another Committer" \
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #3" &&
echo "l4" >two &&
test_tick &&
git commit -a -m "Left #4" &&
GIT_COMMITTER_NAME="Another Committer" \
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #4" &&
echo "l5" >two &&
test_tick &&
git commit -a -m "Left #5" &&
GIT_COMMITTER_NAME="Another Committer" \
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #5" &&
git tag tag-l5 &&
git checkout right &&
Expand Down Expand Up @@ -99,6 +102,8 @@ test_expect_success '[merge] summary/log configuration' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left:
Left #5
Left #4
Expand Down Expand Up @@ -144,6 +149,8 @@ test_expect_success 'merge.log=3 limits shortlog length' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left: (5 commits)
Left #5
Left #4
Expand All @@ -159,6 +166,8 @@ test_expect_success 'merge.log=5 shows all 5 commits' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left:
Left #5
Left #4
Expand All @@ -181,6 +190,8 @@ test_expect_success '--log=3 limits shortlog length' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left: (5 commits)
Left #5
Left #4
Expand All @@ -196,6 +207,8 @@ test_expect_success '--log=5 shows all 5 commits' '
cat >expected <<-EOF &&
Merge branch ${apos}left${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left:
Left #5
Left #4
Expand Down Expand Up @@ -225,6 +238,8 @@ test_expect_success 'fmt-merge-msg -m' '
cat >expected.log <<-EOF &&
Sync with left
By Another Author (3) and A U Thor (2)
via Another Committer
* ${apos}left${apos} of $(pwd):
Left #5
Left #4
Expand Down Expand Up @@ -256,6 +271,8 @@ test_expect_success 'setup: expected shortlog for two branches' '
cat >expected <<-EOF
Merge branches ${apos}left${apos} and ${apos}right${apos}
By Another Author (3) and A U Thor (2)
via Another Committer
* left:
Left #5
Left #4
Expand Down Expand Up @@ -379,6 +396,8 @@ test_expect_success 'merge-msg two tags' '
Common #2
Common #1
By Another Author (3) and A U Thor (2)
via Another Committer
* tag ${apos}tag-l5${apos}:
Left #5
Left #4
Expand Down Expand Up @@ -407,6 +426,8 @@ test_expect_success 'merge-msg tag and branch' '
Common #2
Common #1
By Another Author (3) and A U Thor (2)
via Another Committer
* left:
Left #5
Left #4
Expand Down

0 comments on commit 418a143

Please sign in to comment.