Skip to content

Commit fa2ab86

Browse files
0day-trouble-makergitster
authored andcommitted
format-patch: add '--base' option to record base tree info
Maintainers or third party testers may want to know the exact base tree the patch series applies to. Teach git format-patch a '--base' option to record the base tree info and append it at the end of the first message (either the cover letter or the first patch in the series). The base tree info consists of the "base commit", which is a well-known commit that is part of the stable part of the project history everybody else works off of, and zero or more "prerequisite patches", which are well-known patches in flight that is not yet part of the "base commit" that need to be applied on top of "base commit" in topological order before the patches can be applied. The "base commit" is shown as "base-commit: " followed by the 40-hex of the commit object name. A "prerequisite patch" is shown as "prerequisite-patch-id: " followed by the 40-hex "patch id", which can be obtained by passing the patch through the "git patch-id --stable" command. Imagine that on top of the public commit P, you applied well-known patches X, Y and Z from somebody else, and then built your three-patch series A, B, C, the history would be like: ---P---X---Y---Z---A---B---C With "git format-patch --base=P -3 C" (or variants thereof, e.g. with "--cover-letter" of using "Z..C" instead of "-3 C" to specify the range), the base tree information block is shown at the end of the first message the command outputs (either the first patch, or the cover letter), like this: base-commit: P prerequisite-patch-id: X prerequisite-patch-id: Y prerequisite-patch-id: Z Helped-by: Junio C Hamano <[email protected]> Helped-by: Wu Fengguang <[email protected]> Signed-off-by: Xiaolong Ye <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ded2c09 commit fa2ab86

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

Documentation/git-format-patch.txt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
265265
Output an all-zero hash in each patch's From header instead
266266
of the hash of the commit.
267267

268+
--base=<commit>::
269+
Record the base tree information to identify the state the
270+
patch series applies to. See the BASE TREE INFORMATION section
271+
below for details.
272+
268273
--root::
269274
Treat the revision argument as a <revision range>, even if it
270275
is just a single commit (that would normally be treated as a
@@ -520,6 +525,55 @@ This should help you to submit patches inline using KMail.
520525
5. Back in the compose window: add whatever other text you wish to the
521526
message, complete the addressing and subject fields, and press send.
522527

528+
BASE TREE INFORMATION
529+
---------------------
530+
531+
The base tree information block is used for maintainers or third party
532+
testers to know the exact state the patch series applies to. It consists
533+
of the 'base commit', which is a well-known commit that is part of the
534+
stable part of the project history everybody else works off of, and zero
535+
or more 'prerequisite patches', which are well-known patches in flight
536+
that is not yet part of the 'base commit' that need to be applied on top
537+
of 'base commit' in topological order before the patches can be applied.
538+
539+
The 'base commit' is shown as "base-commit: " followed by the 40-hex of
540+
the commit object name. A 'prerequisite patch' is shown as
541+
"prerequisite-patch-id: " followed by the 40-hex 'patch id', which can
542+
be obtained by passing the patch through the `git patch-id --stable`
543+
command.
544+
545+
Imagine that on top of the public commit P, you applied well-known
546+
patches X, Y and Z from somebody else, and then built your three-patch
547+
series A, B, C, the history would be like:
548+
549+
................................................
550+
---P---X---Y---Z---A---B---C
551+
................................................
552+
553+
With `git format-patch --base=P -3 C` (or variants thereof, e.g. with
554+
`--cover-letter` of using `Z..C` instead of `-3 C` to specify the
555+
range), the base tree information block is shown at the end of the
556+
first message the command outputs (either the first patch, or the
557+
cover letter), like this:
558+
559+
------------
560+
base-commit: P
561+
prerequisite-patch-id: X
562+
prerequisite-patch-id: Y
563+
prerequisite-patch-id: Z
564+
------------
565+
566+
For non-linear topology, such as
567+
568+
................................................
569+
---P---X---A---M---C
570+
\ /
571+
Y---Z---B
572+
................................................
573+
574+
You can also use `git format-patch --base=P -3 C` to generate patches
575+
for A, B and C, and the identifiers for P, X, Y, Z are appended at the
576+
end of the first message.
523577

524578
EXAMPLES
525579
--------

builtin/log.c

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,131 @@ static int from_callback(const struct option *opt, const char *arg, int unset)
11851185
return 0;
11861186
}
11871187

1188+
struct base_tree_info {
1189+
struct object_id base_commit;
1190+
int nr_patch_id, alloc_patch_id;
1191+
struct object_id *patch_id;
1192+
};
1193+
1194+
static struct commit *get_base_commit(const char *base_commit,
1195+
struct commit **list,
1196+
int total)
1197+
{
1198+
struct commit *base = NULL;
1199+
struct commit **rev;
1200+
int i = 0, rev_nr = 0;
1201+
1202+
base = lookup_commit_reference_by_name(base_commit);
1203+
if (!base)
1204+
die(_("Unknown commit %s"), base_commit);
1205+
1206+
ALLOC_ARRAY(rev, total);
1207+
for (i = 0; i < total; i++)
1208+
rev[i] = list[i];
1209+
1210+
rev_nr = total;
1211+
/*
1212+
* Get merge base through pair-wise computations
1213+
* and store it in rev[0].
1214+
*/
1215+
while (rev_nr > 1) {
1216+
for (i = 0; i < rev_nr / 2; i++) {
1217+
struct commit_list *merge_base;
1218+
merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
1219+
if (!merge_base || merge_base->next)
1220+
die(_("Failed to find exact merge base"));
1221+
1222+
rev[i] = merge_base->item;
1223+
}
1224+
1225+
if (rev_nr % 2)
1226+
rev[i] = rev[2 * i];
1227+
rev_nr = (rev_nr + 1) / 2;
1228+
}
1229+
1230+
if (!in_merge_bases(base, rev[0]))
1231+
die(_("base commit should be the ancestor of revision list"));
1232+
1233+
for (i = 0; i < total; i++) {
1234+
if (base == list[i])
1235+
die(_("base commit shouldn't be in revision list"));
1236+
}
1237+
1238+
free(rev);
1239+
return base;
1240+
}
1241+
1242+
static void prepare_bases(struct base_tree_info *bases,
1243+
struct commit *base,
1244+
struct commit **list,
1245+
int total)
1246+
{
1247+
struct commit *commit;
1248+
struct rev_info revs;
1249+
struct diff_options diffopt;
1250+
int i;
1251+
1252+
if (!base)
1253+
return;
1254+
1255+
diff_setup(&diffopt);
1256+
DIFF_OPT_SET(&diffopt, RECURSIVE);
1257+
diff_setup_done(&diffopt);
1258+
1259+
oidcpy(&bases->base_commit, &base->object.oid);
1260+
1261+
init_revisions(&revs, NULL);
1262+
revs.max_parents = 1;
1263+
revs.topo_order = 1;
1264+
for (i = 0; i < total; i++) {
1265+
list[i]->object.flags &= ~UNINTERESTING;
1266+
add_pending_object(&revs, &list[i]->object, "rev_list");
1267+
list[i]->util = (void *)1;
1268+
}
1269+
base->object.flags |= UNINTERESTING;
1270+
add_pending_object(&revs, &base->object, "base");
1271+
1272+
if (prepare_revision_walk(&revs))
1273+
die(_("revision walk setup failed"));
1274+
/*
1275+
* Traverse the commits list, get prerequisite patch ids
1276+
* and stuff them in bases structure.
1277+
*/
1278+
while ((commit = get_revision(&revs)) != NULL) {
1279+
unsigned char sha1[20];
1280+
struct object_id *patch_id;
1281+
if (commit->util)
1282+
continue;
1283+
if (commit_patch_id(commit, &diffopt, sha1))
1284+
die(_("cannot get patch id"));
1285+
ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id);
1286+
patch_id = bases->patch_id + bases->nr_patch_id;
1287+
hashcpy(patch_id->hash, sha1);
1288+
bases->nr_patch_id++;
1289+
}
1290+
}
1291+
1292+
static void print_bases(struct base_tree_info *bases)
1293+
{
1294+
int i;
1295+
1296+
/* Only do this once, either for the cover or for the first one */
1297+
if (is_null_oid(&bases->base_commit))
1298+
return;
1299+
1300+
/* Show the base commit */
1301+
printf("base-commit: %s\n", oid_to_hex(&bases->base_commit));
1302+
1303+
/* Show the prerequisite patches */
1304+
for (i = bases->nr_patch_id - 1; i >= 0; i--)
1305+
printf("prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i]));
1306+
1307+
free(bases->patch_id);
1308+
bases->nr_patch_id = 0;
1309+
bases->alloc_patch_id = 0;
1310+
oidclr(&bases->base_commit);
1311+
}
1312+
11881313
int cmd_format_patch(int argc, const char **argv, const char *prefix)
11891314
{
11901315
struct commit *commit;
@@ -1209,6 +1334,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
12091334
int reroll_count = -1;
12101335
char *branch_name = NULL;
12111336
char *from = NULL;
1337+
char *base_commit = NULL;
1338+
struct base_tree_info bases;
1339+
12121340
const struct option builtin_format_patch_options[] = {
12131341
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
12141342
N_("use [PATCH n/m] even with a single patch"),
@@ -1271,6 +1399,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
12711399
PARSE_OPT_OPTARG, thread_callback },
12721400
OPT_STRING(0, "signature", &signature, N_("signature"),
12731401
N_("add a signature")),
1402+
OPT_STRING(0, "base", &base_commit, N_("base-commit"),
1403+
N_("add prerequisite tree info to the patch series")),
12741404
OPT_FILENAME(0, "signature-file", &signature_file,
12751405
N_("add a signature from a file")),
12761406
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
@@ -1507,6 +1637,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
15071637
signature = strbuf_detach(&buf, NULL);
15081638
}
15091639

1640+
memset(&bases, 0, sizeof(bases));
1641+
if (base_commit) {
1642+
struct commit *base = get_base_commit(base_commit, list, nr);
1643+
reset_revision_walk();
1644+
prepare_bases(&bases, base, list, nr);
1645+
}
1646+
15101647
if (in_reply_to || thread || cover_letter)
15111648
rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
15121649
if (in_reply_to) {
@@ -1520,6 +1657,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
15201657
gen_message_id(&rev, "cover");
15211658
make_cover_letter(&rev, use_stdout,
15221659
origin, nr, list, branch_name, quiet);
1660+
print_bases(&bases);
15231661
total++;
15241662
start_number--;
15251663
}
@@ -1585,6 +1723,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
15851723
rev.mime_boundary);
15861724
else
15871725
print_signature();
1726+
print_bases(&bases);
15881727
}
15891728
if (!use_stdout)
15901729
fclose(stdout);

t/t4014-format-patch.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,4 +1460,51 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
14601460
test_path_is_dir patchset
14611461
'
14621462

1463+
test_expect_success 'format-patch --base' '
1464+
git checkout side &&
1465+
git format-patch --stdout --base=HEAD~3 -1 >patch &&
1466+
grep "^base-commit:" patch >actual &&
1467+
grep "^prerequisite-patch-id:" patch >>actual &&
1468+
echo "base-commit: $(git rev-parse HEAD~3)" >expected &&
1469+
echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
1470+
echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
1471+
test_cmp expected actual
1472+
'
1473+
1474+
test_expect_success 'format-patch --base errors out when base commit is in revision list' '
1475+
test_must_fail git format-patch --base=HEAD -2 &&
1476+
test_must_fail git format-patch --base=HEAD~1 -2 &&
1477+
git format-patch --stdout --base=HEAD~2 -2 >patch &&
1478+
grep "^base-commit:" patch >actual &&
1479+
echo "base-commit: $(git rev-parse HEAD~2)" >expected &&
1480+
test_cmp expected actual
1481+
'
1482+
1483+
test_expect_success 'format-patch --base errors out when base commit is not ancestor of revision list' '
1484+
# For history as below:
1485+
#
1486+
# ---Q---P---Z---Y---*---X
1487+
# \ /
1488+
# ------------W
1489+
#
1490+
# If "format-patch Z..X" is given, P and Z can not be specified as the base commit
1491+
git checkout -b topic1 master &&
1492+
git rev-parse HEAD >commit-id-base &&
1493+
test_commit P &&
1494+
git rev-parse HEAD >commit-id-P &&
1495+
test_commit Z &&
1496+
git rev-parse HEAD >commit-id-Z &&
1497+
test_commit Y &&
1498+
git checkout -b topic2 master &&
1499+
test_commit W &&
1500+
git merge topic1 &&
1501+
test_commit X &&
1502+
test_must_fail git format-patch --base=$(cat commit-id-P) -3 &&
1503+
test_must_fail git format-patch --base=$(cat commit-id-Z) -3 &&
1504+
git format-patch --stdout --base=$(cat commit-id-base) -3 >patch &&
1505+
grep "^base-commit:" patch >actual &&
1506+
echo "base-commit: $(cat commit-id-base)" >expected &&
1507+
test_cmp expected actual
1508+
'
1509+
14631510
test_done

0 commit comments

Comments
 (0)