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 <gitster@pobox.com>

Junio C Hamano committed Mar 13, 2012 at 10:00 UTC 418a1435f19b8105de2ee94b54cc2962bc9b5f03
2 files changed +134 -7
builtin/fmt-merge-msg.c
+110 -4
@@ -27,6 +27,8 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)
27 merge_log_config = DEFAULT_MERGE_LOG_LEN;
28 } else if (!strcmp(key, "merge.branchdesc")) {
29 use_branch_desc = git_config_bool(key, value);
30 + } else {
31 + return git_default_config(key, value, cb);
32 }
33 return 0;
34 }
@@ -180,6 +182,101 @@ static void add_branch_desc(struct strbuf *out, const char *name)
182 strbuf_release(&desc);
183 }
184
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 +
280 static void shortlog(const char *name,
281 struct origin_data *origin_data,
282 struct commit *head,
@@ -190,6 +287,8 @@ static void shortlog(const char *name,
287 struct commit *commit;
288 struct object *branch;
289 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;
292 int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
293 struct strbuf sb = STRBUF_INIT;
294 const unsigned char *sha1 = origin_data->sha1;
@@ -199,7 +298,6 @@ static void shortlog(const char *name,
298 return;
299
300 setup_revisions(0, NULL, rev, NULL);
202 - rev->ignore_merges = 1;
301 add_pending_object(rev, branch, name);
302 add_pending_object(rev, &head->object, "^HEAD");
303 head->object.flags |= UNINTERESTING;
@@ -208,10 +306,15 @@ static void shortlog(const char *name,
306 while ((commit = get_revision(rev)) != NULL) {
307 struct pretty_print_context ctx = {0};
308
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);
312 continue;
214 -
313 + }
314 + if (!count)
315 + /* the 'tip' committer */
316 + record_person('c', &committers, commit);
317 + record_person('a', &authors, commit);
318 count++;
319 if (subjects.nr > limit)
320 continue;
@@ -226,6 +329,7 @@ static void shortlog(const char *name,
329 string_list_append(&subjects, strbuf_detach(&sb, NULL));
330 }
331
332 + add_people_info(out, &authors, &committers);
333 if (count > limit)
334 strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
335 else
@@ -246,6 +350,8 @@ static void shortlog(const char *name,
350 rev->commits = NULL;
351 rev->pending.nr = 0;
352
353 + string_list_clear(&authors, 0);
354 + string_list_clear(&committers, 0);
355 string_list_clear(&subjects, 0);
356 }
357
t/t6200-fmt-merge-msg.sh
+24 -3
@@ -35,15 +35,18 @@ test_expect_success setup '
35
36 echo "l3" >two &&
37 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" &&
40
41 echo "l4" >two &&
42 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" &&
45
46 echo "l5" >two &&
47 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" &&
50 git tag tag-l5 &&
51
52 git checkout right &&
@@ -99,6 +102,8 @@ test_expect_success '[merge] summary/log configuration' '
102 cat >expected <<-EOF &&
103 Merge branch ${apos}left${apos}
104
105 + By Another Author (3) and A U Thor (2)
106 + via Another Committer
107 * left:
108 Left #5
109 Left #4
@@ -144,6 +149,8 @@ test_expect_success 'merge.log=3 limits shortlog length' '
149 cat >expected <<-EOF &&
150 Merge branch ${apos}left${apos}
151
152 + By Another Author (3) and A U Thor (2)
153 + via Another Committer
154 * left: (5 commits)
155 Left #5
156 Left #4
@@ -159,6 +166,8 @@ test_expect_success 'merge.log=5 shows all 5 commits' '
166 cat >expected <<-EOF &&
167 Merge branch ${apos}left${apos}
168
169 + By Another Author (3) and A U Thor (2)
170 + via Another Committer
171 * left:
172 Left #5
173 Left #4
@@ -181,6 +190,8 @@ test_expect_success '--log=3 limits shortlog length' '
190 cat >expected <<-EOF &&
191 Merge branch ${apos}left${apos}
192
193 + By Another Author (3) and A U Thor (2)
194 + via Another Committer
195 * left: (5 commits)
196 Left #5
197 Left #4
@@ -196,6 +207,8 @@ test_expect_success '--log=5 shows all 5 commits' '
207 cat >expected <<-EOF &&
208 Merge branch ${apos}left${apos}
209
210 + By Another Author (3) and A U Thor (2)
211 + via Another Committer
212 * left:
213 Left #5
214 Left #4
@@ -225,6 +238,8 @@ test_expect_success 'fmt-merge-msg -m' '
238 cat >expected.log <<-EOF &&
239 Sync with left
240
241 + By Another Author (3) and A U Thor (2)
242 + via Another Committer
243 * ${apos}left${apos} of $(pwd):
244 Left #5
245 Left #4
@@ -256,6 +271,8 @@ test_expect_success 'setup: expected shortlog for two branches' '
271 cat >expected <<-EOF
272 Merge branches ${apos}left${apos} and ${apos}right${apos}
273
274 + By Another Author (3) and A U Thor (2)
275 + via Another Committer
276 * left:
277 Left #5
278 Left #4
@@ -379,6 +396,8 @@ test_expect_success 'merge-msg two tags' '
396 Common #2
397 Common #1
398
399 + By Another Author (3) and A U Thor (2)
400 + via Another Committer
401 * tag ${apos}tag-l5${apos}:
402 Left #5
403 Left #4
@@ -407,6 +426,8 @@ test_expect_success 'merge-msg tag and branch' '
426 Common #2
427 Common #1
428
429 + By Another Author (3) and A U Thor (2)
430 + via Another Committer
431 * left:
432 Left #5
433 Left #4