Skip to content

Commit 418a143

Browse files
committed
fmt-merge-msg: show those involved in a merged series
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 <[email protected]>
1 parent ead8eb8 commit 418a143

File tree

2 files changed

+134
-7
lines changed

2 files changed

+134
-7
lines changed

builtin/fmt-merge-msg.c

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
2727
merge_log_config = DEFAULT_MERGE_LOG_LEN;
2828
} else if (!strcmp(key, "merge.branchdesc")) {
2929
use_branch_desc = git_config_bool(key, value);
30+
} else {
31+
return git_default_config(key, value, cb);
3032
}
3133
return 0;
3234
}
@@ -180,6 +182,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
180182
strbuf_release(&desc);
181183
}
182184

185+
#define util_as_integral(elem) ((intptr_t)((elem)->util))
186+
187+
static void record_person(int which, struct string_list *people,
188+
struct commit *commit)
189+
{
190+
char name_buf[MAX_GITNAME], *name, *name_end;
191+
struct string_list_item *elem;
192+
const char *field = (which == 'a') ? "\nauthor " : "\ncommitter ";
193+
194+
name = strstr(commit->buffer, field);
195+
if (!name)
196+
return;
197+
name += strlen(field);
198+
name_end = strchrnul(name, '<');
199+
if (*name_end)
200+
name_end--;
201+
while (isspace(*name_end) && name <= name_end)
202+
name_end--;
203+
if (name_end < name || name + MAX_GITNAME <= name_end)
204+
return;
205+
memcpy(name_buf, name, name_end - name + 1);
206+
name_buf[name_end - name + 1] = '\0';
207+
208+
elem = string_list_lookup(people, name_buf);
209+
if (!elem) {
210+
elem = string_list_insert(people, name_buf);
211+
elem->util = (void *)0;
212+
}
213+
elem->util = (void*)(util_as_integral(elem) + 1);
214+
}
215+
216+
static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
217+
{
218+
const struct string_list_item *a = a_, *b = b_;
219+
return util_as_integral(b) - util_as_integral(a);
220+
}
221+
222+
static void add_people_count(struct strbuf *out, struct string_list *people)
223+
{
224+
if (people->nr == 1)
225+
strbuf_addf(out, "%s", people->items[0].string);
226+
else if (people->nr == 2)
227+
strbuf_addf(out, "%s (%d) and %s (%d)",
228+
people->items[0].string,
229+
(int)util_as_integral(&people->items[0]),
230+
people->items[1].string,
231+
(int)util_as_integral(&people->items[1]));
232+
else if (people->nr)
233+
strbuf_addf(out, "%s (%d) and others",
234+
people->items[0].string,
235+
(int)util_as_integral(&people->items[0]));
236+
}
237+
238+
static void credit_people(struct strbuf *out,
239+
struct string_list *them,
240+
int kind)
241+
{
242+
const char *label;
243+
const char *me;
244+
245+
if (kind == 'a') {
246+
label = "\nBy ";
247+
me = git_author_info(IDENT_NO_DATE);
248+
} else {
249+
label = "\nvia ";
250+
me = git_committer_info(IDENT_NO_DATE);
251+
}
252+
253+
if (!them->nr ||
254+
(them->nr == 1 &&
255+
me &&
256+
(me = skip_prefix(me, them->items->string)) != NULL &&
257+
skip_prefix(me, " <")))
258+
return;
259+
strbuf_addstr(out, label);
260+
add_people_count(out, them);
261+
}
262+
263+
static void add_people_info(struct strbuf *out,
264+
struct string_list *authors,
265+
struct string_list *committers)
266+
{
267+
if (authors->nr)
268+
qsort(authors->items,
269+
authors->nr, sizeof(authors->items[0]),
270+
cmp_string_list_util_as_integral);
271+
if (committers->nr)
272+
qsort(committers->items,
273+
committers->nr, sizeof(committers->items[0]),
274+
cmp_string_list_util_as_integral);
275+
276+
credit_people(out, authors, 'a');
277+
credit_people(out, committers, 'c');
278+
}
279+
183280
static void shortlog(const char *name,
184281
struct origin_data *origin_data,
185282
struct commit *head,
@@ -190,6 +287,8 @@ static void shortlog(const char *name,
190287
struct commit *commit;
191288
struct object *branch;
192289
struct string_list subjects = STRING_LIST_INIT_DUP;
290+
struct string_list authors = STRING_LIST_INIT_DUP;
291+
struct string_list committers = STRING_LIST_INIT_DUP;
193292
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
194293
struct strbuf sb = STRBUF_INIT;
195294
const unsigned char *sha1 = origin_data->sha1;
@@ -199,7 +298,6 @@ static void shortlog(const char *name,
199298
return;
200299

201300
setup_revisions(0, NULL, rev, NULL);
202-
rev->ignore_merges = 1;
203301
add_pending_object(rev, branch, name);
204302
add_pending_object(rev, &head->object, "^HEAD");
205303
head->object.flags |= UNINTERESTING;
@@ -208,10 +306,15 @@ static void shortlog(const char *name,
208306
while ((commit = get_revision(rev)) != NULL) {
209307
struct pretty_print_context ctx = {0};
210308

211-
/* ignore merges */
212-
if (commit->parents && commit->parents->next)
309+
if (commit->parents && commit->parents->next) {
310+
/* do not list a merge but count committer */
311+
record_person('c', &committers, commit);
213312
continue;
214-
313+
}
314+
if (!count)
315+
/* the 'tip' committer */
316+
record_person('c', &committers, commit);
317+
record_person('a', &authors, commit);
215318
count++;
216319
if (subjects.nr > limit)
217320
continue;
@@ -226,6 +329,7 @@ static void shortlog(const char *name,
226329
string_list_append(&subjects, strbuf_detach(&sb, NULL));
227330
}
228331

332+
add_people_info(out, &authors, &committers);
229333
if (count > limit)
230334
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
231335
else
@@ -246,6 +350,8 @@ static void shortlog(const char *name,
246350
rev->commits = NULL;
247351
rev->pending.nr = 0;
248352

353+
string_list_clear(&authors, 0);
354+
string_list_clear(&committers, 0);
249355
string_list_clear(&subjects, 0);
250356
}
251357

t/t6200-fmt-merge-msg.sh

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,18 @@ test_expect_success setup '
3535
3636
echo "l3" >two &&
3737
test_tick &&
38-
git commit -a -m "Left #3" &&
38+
GIT_COMMITTER_NAME="Another Committer" \
39+
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #3" &&
3940
4041
echo "l4" >two &&
4142
test_tick &&
42-
git commit -a -m "Left #4" &&
43+
GIT_COMMITTER_NAME="Another Committer" \
44+
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #4" &&
4345
4446
echo "l5" >two &&
4547
test_tick &&
46-
git commit -a -m "Left #5" &&
48+
GIT_COMMITTER_NAME="Another Committer" \
49+
GIT_AUTHOR_NAME="Another Author" git commit -a -m "Left #5" &&
4750
git tag tag-l5 &&
4851
4952
git checkout right &&
@@ -99,6 +102,8 @@ test_expect_success '[merge] summary/log configuration' '
99102
cat >expected <<-EOF &&
100103
Merge branch ${apos}left${apos}
101104
105+
By Another Author (3) and A U Thor (2)
106+
via Another Committer
102107
* left:
103108
Left #5
104109
Left #4
@@ -144,6 +149,8 @@ test_expect_success 'merge.log=3 limits shortlog length' '
144149
cat >expected <<-EOF &&
145150
Merge branch ${apos}left${apos}
146151
152+
By Another Author (3) and A U Thor (2)
153+
via Another Committer
147154
* left: (5 commits)
148155
Left #5
149156
Left #4
@@ -159,6 +166,8 @@ test_expect_success 'merge.log=5 shows all 5 commits' '
159166
cat >expected <<-EOF &&
160167
Merge branch ${apos}left${apos}
161168
169+
By Another Author (3) and A U Thor (2)
170+
via Another Committer
162171
* left:
163172
Left #5
164173
Left #4
@@ -181,6 +190,8 @@ test_expect_success '--log=3 limits shortlog length' '
181190
cat >expected <<-EOF &&
182191
Merge branch ${apos}left${apos}
183192
193+
By Another Author (3) and A U Thor (2)
194+
via Another Committer
184195
* left: (5 commits)
185196
Left #5
186197
Left #4
@@ -196,6 +207,8 @@ test_expect_success '--log=5 shows all 5 commits' '
196207
cat >expected <<-EOF &&
197208
Merge branch ${apos}left${apos}
198209
210+
By Another Author (3) and A U Thor (2)
211+
via Another Committer
199212
* left:
200213
Left #5
201214
Left #4
@@ -225,6 +238,8 @@ test_expect_success 'fmt-merge-msg -m' '
225238
cat >expected.log <<-EOF &&
226239
Sync with left
227240
241+
By Another Author (3) and A U Thor (2)
242+
via Another Committer
228243
* ${apos}left${apos} of $(pwd):
229244
Left #5
230245
Left #4
@@ -256,6 +271,8 @@ test_expect_success 'setup: expected shortlog for two branches' '
256271
cat >expected <<-EOF
257272
Merge branches ${apos}left${apos} and ${apos}right${apos}
258273
274+
By Another Author (3) and A U Thor (2)
275+
via Another Committer
259276
* left:
260277
Left #5
261278
Left #4
@@ -379,6 +396,8 @@ test_expect_success 'merge-msg two tags' '
379396
Common #2
380397
Common #1
381398
399+
By Another Author (3) and A U Thor (2)
400+
via Another Committer
382401
* tag ${apos}tag-l5${apos}:
383402
Left #5
384403
Left #4
@@ -407,6 +426,8 @@ test_expect_success 'merge-msg tag and branch' '
407426
Common #2
408427
Common #1
409428
429+
By Another Author (3) and A U Thor (2)
430+
via Another Committer
410431
* left:
411432
Left #5
412433
Left #4

0 commit comments

Comments
 (0)