Skip to content

Commit 7efba5f

Browse files
jacob-kellergitster
authored andcommitted
format-patch: teach format.useAutoBase "whenAble" option
The format.useAutoBase configuration option exists to allow users to enable '--base=auto' for format-patch by default. This can sometimes lead to poor workflow, due to unexpected failures when attempting to format an ancient patch: $ git format-patch -1 <an old commit> fatal: base commit shouldn't be in revision list This can be very confusing, as it is not necessarily immediately obvious that the user requested a --base (since this was in the configuration, not on the command line). We do want --base=auto to fail when it cannot provide a suitable base, as it would be equally confusing if a formatted patch did not include the base information when it was requested. Teach format.useAutoBase a new mode, "whenAble". This mode will cause format-patch to attempt to include a base commit when it can. However, if no valid base commit can be found, then format-patch will continue formatting the patch without a base commit. In order to avoid making yet another branch name unusable with --base, do not teach --base=whenAble or --base=whenable. Instead, refactor the base_commit option to use a callback, and rely on the global configuration variable auto_base. This does mean that a user cannot request this optional base commit generation from the command line. However, this is likely not too valuable. If the user requests base information manually, they will be immediately informed of the failure to acquire a suitable base commit. This allows the user to make an informed choice about whether to continue the format. Add tests to cover the new mode of operation for --base. Signed-off-by: Jacob Keller <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 9bc233a commit 7efba5f

File tree

3 files changed

+130
-26
lines changed

3 files changed

+130
-26
lines changed

Documentation/config/format.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ format.outputDirectory::
9696

9797
format.useAutoBase::
9898
A boolean value which lets you enable the `--base=auto` option of
99-
format-patch by default.
99+
format-patch by default. Can also be set to "whenAble" to allow
100+
enabling `--base=auto` if a suitable base is available, but to skip
101+
adding base info otherwise without the format dying.
100102

101103
format.notes::
102104
Provides the default value for the `--notes` option to

builtin/log.c

Lines changed: 105 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,15 @@ enum cover_from_description {
805805
COVER_FROM_AUTO
806806
};
807807

808+
enum auto_base_setting {
809+
AUTO_BASE_NEVER,
810+
AUTO_BASE_ALWAYS,
811+
AUTO_BASE_WHEN_ABLE
812+
};
813+
808814
static enum thread_level thread;
809815
static int do_signoff;
810-
static int base_auto;
816+
static enum auto_base_setting auto_base;
811817
static char *from;
812818
static const char *signature = git_version_string;
813819
static const char *signature_file;
@@ -906,7 +912,11 @@ static int git_format_config(const char *var, const char *value, void *cb)
906912
if (!strcmp(var, "format.outputdirectory"))
907913
return git_config_string(&config_output_directory, var, value);
908914
if (!strcmp(var, "format.useautobase")) {
909-
base_auto = git_config_bool(var, value);
915+
if (value && !strcasecmp(value, "whenAble")) {
916+
auto_base = AUTO_BASE_WHEN_ABLE;
917+
return 0;
918+
}
919+
auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER;
910920
return 0;
911921
}
912922
if (!strcmp(var, "format.from")) {
@@ -1424,6 +1434,23 @@ static int from_callback(const struct option *opt, const char *arg, int unset)
14241434
return 0;
14251435
}
14261436

1437+
static int base_callback(const struct option *opt, const char *arg, int unset)
1438+
{
1439+
const char **base_commit = opt->value;
1440+
1441+
if (unset) {
1442+
auto_base = AUTO_BASE_NEVER;
1443+
*base_commit = NULL;
1444+
} else if (!strcmp(arg, "auto")) {
1445+
auto_base = AUTO_BASE_ALWAYS;
1446+
*base_commit = NULL;
1447+
} else {
1448+
auto_base = AUTO_BASE_NEVER;
1449+
*base_commit = arg;
1450+
}
1451+
return 0;
1452+
}
1453+
14271454
struct base_tree_info {
14281455
struct object_id base_commit;
14291456
int nr_patch_id, alloc_patch_id;
@@ -1436,33 +1463,69 @@ static struct commit *get_base_commit(const char *base_commit,
14361463
{
14371464
struct commit *base = NULL;
14381465
struct commit **rev;
1439-
int i = 0, rev_nr = 0;
1466+
int i = 0, rev_nr = 0, auto_select, die_on_failure;
14401467

1441-
if (base_commit && strcmp(base_commit, "auto")) {
1468+
switch (auto_base) {
1469+
case AUTO_BASE_NEVER:
1470+
if (base_commit) {
1471+
auto_select = 0;
1472+
die_on_failure = 1;
1473+
} else {
1474+
/* no base information is requested */
1475+
return NULL;
1476+
}
1477+
break;
1478+
case AUTO_BASE_ALWAYS:
1479+
case AUTO_BASE_WHEN_ABLE:
1480+
if (base_commit) {
1481+
BUG("requested automatic base selection but a commit was provided");
1482+
} else {
1483+
auto_select = 1;
1484+
die_on_failure = auto_base == AUTO_BASE_ALWAYS;
1485+
}
1486+
break;
1487+
default:
1488+
BUG("unexpected automatic base selection method");
1489+
}
1490+
1491+
if (!auto_select) {
14421492
base = lookup_commit_reference_by_name(base_commit);
14431493
if (!base)
14441494
die(_("unknown commit %s"), base_commit);
1445-
} else if ((base_commit && !strcmp(base_commit, "auto"))) {
1495+
} else {
14461496
struct branch *curr_branch = branch_get(NULL);
14471497
const char *upstream = branch_get_upstream(curr_branch, NULL);
14481498
if (upstream) {
14491499
struct commit_list *base_list;
14501500
struct commit *commit;
14511501
struct object_id oid;
14521502

1453-
if (get_oid(upstream, &oid))
1454-
die(_("failed to resolve '%s' as a valid ref"), upstream);
1503+
if (get_oid(upstream, &oid)) {
1504+
if (die_on_failure)
1505+
die(_("failed to resolve '%s' as a valid ref"), upstream);
1506+
else
1507+
return NULL;
1508+
}
14551509
commit = lookup_commit_or_die(&oid, "upstream base");
14561510
base_list = get_merge_bases_many(commit, total, list);
14571511
/* There should be one and only one merge base. */
1458-
if (!base_list || base_list->next)
1459-
die(_("could not find exact merge base"));
1512+
if (!base_list || base_list->next) {
1513+
if (die_on_failure) {
1514+
die(_("could not find exact merge base"));
1515+
} else {
1516+
free_commit_list(base_list);
1517+
return NULL;
1518+
}
1519+
}
14601520
base = base_list->item;
14611521
free_commit_list(base_list);
14621522
} else {
1463-
die(_("failed to get upstream, if you want to record base commit automatically,\n"
1464-
"please use git branch --set-upstream-to to track a remote branch.\n"
1465-
"Or you could specify base commit by --base=<base-commit-id> manually"));
1523+
if (die_on_failure)
1524+
die(_("failed to get upstream, if you want to record base commit automatically,\n"
1525+
"please use git branch --set-upstream-to to track a remote branch.\n"
1526+
"Or you could specify base commit by --base=<base-commit-id> manually"));
1527+
else
1528+
return NULL;
14661529
}
14671530
}
14681531

@@ -1479,8 +1542,14 @@ static struct commit *get_base_commit(const char *base_commit,
14791542
for (i = 0; i < rev_nr / 2; i++) {
14801543
struct commit_list *merge_base;
14811544
merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
1482-
if (!merge_base || merge_base->next)
1483-
die(_("failed to find exact merge base"));
1545+
if (!merge_base || merge_base->next) {
1546+
if (die_on_failure) {
1547+
die(_("failed to find exact merge base"));
1548+
} else {
1549+
free(rev);
1550+
return NULL;
1551+
}
1552+
}
14841553

14851554
rev[i] = merge_base->item;
14861555
}
@@ -1490,12 +1559,24 @@ static struct commit *get_base_commit(const char *base_commit,
14901559
rev_nr = DIV_ROUND_UP(rev_nr, 2);
14911560
}
14921561

1493-
if (!in_merge_bases(base, rev[0]))
1494-
die(_("base commit should be the ancestor of revision list"));
1562+
if (!in_merge_bases(base, rev[0])) {
1563+
if (die_on_failure) {
1564+
die(_("base commit should be the ancestor of revision list"));
1565+
} else {
1566+
free(rev);
1567+
return NULL;
1568+
}
1569+
}
14951570

14961571
for (i = 0; i < total; i++) {
1497-
if (base == list[i])
1498-
die(_("base commit shouldn't be in revision list"));
1572+
if (base == list[i]) {
1573+
if (die_on_failure) {
1574+
die(_("base commit shouldn't be in revision list"));
1575+
} else {
1576+
free(rev);
1577+
return NULL;
1578+
}
1579+
}
14991580
}
15001581

15011582
free(rev);
@@ -1638,6 +1719,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
16381719
char *branch_name = NULL;
16391720
char *base_commit = NULL;
16401721
struct base_tree_info bases;
1722+
struct commit *base;
16411723
int show_progress = 0;
16421724
struct progress *progress = NULL;
16431725
struct oid_array idiff_prev = OID_ARRAY_INIT;
@@ -1714,8 +1796,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
17141796
PARSE_OPT_OPTARG, thread_callback),
17151797
OPT_STRING(0, "signature", &signature, N_("signature"),
17161798
N_("add a signature")),
1717-
OPT_STRING(0, "base", &base_commit, N_("base-commit"),
1718-
N_("add prerequisite tree info to the patch series")),
1799+
OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"),
1800+
N_("add prerequisite tree info to the patch series"),
1801+
0, base_callback),
17191802
OPT_FILENAME(0, "signature-file", &signature_file,
17201803
N_("add a signature from a file")),
17211804
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
@@ -1752,9 +1835,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
17521835
s_r_opt.def = "HEAD";
17531836
s_r_opt.revarg_opt = REVARG_COMMITTISH;
17541837

1755-
if (base_auto)
1756-
base_commit = "auto";
1757-
17581838
if (default_attach) {
17591839
rev.mime_boundary = default_attach;
17601840
rev.no_inline = 1;
@@ -2018,8 +2098,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
20182098
}
20192099

20202100
memset(&bases, 0, sizeof(bases));
2021-
if (base_commit) {
2022-
struct commit *base = get_base_commit(base_commit, list, nr);
2101+
base = get_base_commit(base_commit, list, nr);
2102+
if (base) {
20232103
reset_revision_walk();
20242104
clear_object_flags(UNINTERESTING);
20252105
prepare_bases(&bases, base, list, nr);

t/t4014-format-patch.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,12 @@ test_expect_success 'format-patch errors out when history involves criss-cross'
20372037
test_must_fail git format-patch --base=auto -1
20382038
'
20392039

2040+
test_expect_success 'format-patch format.useAutoBase whenAble history involves criss-cross' '
2041+
test_config format.useAutoBase whenAble &&
2042+
git format-patch -1 >patch &&
2043+
! grep "^base-commit:" patch
2044+
'
2045+
20402046
test_expect_success 'format-patch format.useAutoBase option' '
20412047
git checkout local &&
20422048
test_config format.useAutoBase true &&
@@ -2047,6 +2053,16 @@ test_expect_success 'format-patch format.useAutoBase option' '
20472053
test_cmp expect actual
20482054
'
20492055

2056+
test_expect_success 'format-patch format.useAutoBase option with whenAble' '
2057+
git checkout local &&
2058+
test_config format.useAutoBase whenAble &&
2059+
git format-patch --stdout -1 >patch &&
2060+
grep "^base-commit:" patch >actual &&
2061+
git rev-parse upstream >commit-id-base &&
2062+
echo "base-commit: $(cat commit-id-base)" >expect &&
2063+
test_cmp expect actual
2064+
'
2065+
20502066
test_expect_success 'format-patch --base overrides format.useAutoBase' '
20512067
test_config format.useAutoBase true &&
20522068
git format-patch --stdout --base=HEAD~1 -1 >patch &&
@@ -2062,6 +2078,12 @@ test_expect_success 'format-patch --no-base overrides format.useAutoBase' '
20622078
! grep "^base-commit:" patch
20632079
'
20642080

2081+
test_expect_success 'format-patch --no-base overrides format.useAutoBase whenAble' '
2082+
test_config format.useAutoBase whenAble &&
2083+
git format-patch --stdout --no-base -1 >patch &&
2084+
! grep "^base-commit:" patch
2085+
'
2086+
20652087
test_expect_success 'format-patch --base with --attach' '
20662088
git format-patch --attach=mimemime --stdout --base=HEAD~ -1 >patch &&
20672089
sed -n -e "/^base-commit:/s/.*/1/p" -e "/^---*mimemime--$/s/.*/2/p" \

0 commit comments

Comments
 (0)