* [PATCH v5 2/4] format-patch: add '--base' option to record base tree info
2016-04-22 5:42 [PATCH v5 0/4] Add --base option to git-format-patch to record base tree info Xiaolong Ye
2016-04-22 5:42 ` [PATCH v5 1/4] patch-ids: make commit_patch_id() a public helper function Xiaolong Ye
@ 2016-04-22 5:42 ` Xiaolong Ye
2016-04-22 21:39 ` Junio C Hamano
` (2 more replies)
2016-04-22 5:42 ` [PATCH v5 3/4] format-patch: introduce --base=auto option Xiaolong Ye
2016-04-22 5:42 ` [PATCH v5 4/4] format-patch: introduce format.useAutoBase configuration Xiaolong Ye
3 siblings, 3 replies; 12+ messages in thread
From: Xiaolong Ye @ 2016-04-22 5:42 UTC (permalink / raw)
To: git, Junio C Hamano
Cc: fengguang.wu, ying.huang, philip.li, julie.du, Xiaolong Ye
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 <gitster@pobox.com>
Helped-by: Wu Fengguang <fengguang.wu@intel.com>
Signed-off-by: Xiaolong Ye <xiaolong.ye@intel.com>
---
Documentation/git-format-patch.txt | 54 ++++++++++++++
builtin/log.c | 139 +++++++++++++++++++++++++++++++++++++
t/t4014-format-patch.sh | 15 ++++
3 files changed, 208 insertions(+)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 6821441..1d790f1 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -265,6 +265,11 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
Output an all-zero hash in each patch's From header instead
of the hash of the commit.
+--base=<commit>::
+ Record the base tree information to identify the state the
+ patch series applies to. See the BASE TREE INFORMATION section
+ below for details.
+
--root::
Treat the revision argument as a <revision range>, even if it
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.
5. Back in the compose window: add whatever other text you wish to the
message, complete the addressing and subject fields, and press send.
+BASE TREE INFORMATION
+---------------------
+
+The base tree information block is used for maintainers or third party
+testers to know the exact state the patch series applies to. It 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
+------------
+
+For non-linear topology, such as
+
+................................................
+---P---X---A---M---C
+ \ /
+ Y---Z---B
+................................................
+
+You can also use `git format-patch --base=P -3 C` to generate patches
+for A, B and C, and the identifiers for P, X, Y, Z are appended at the
+end of the first message.
EXAMPLES
--------
diff --git a/builtin/log.c b/builtin/log.c
index dff3fbb..ee332ab 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1191,6 +1191,131 @@ static int from_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+struct base_tree_info {
+ struct object_id base_commit;
+ int nr_patch_id, alloc_patch_id;
+ struct object_id *patch_id;
+};
+
+static struct commit *get_base_commit(const char *base_commit,
+ struct commit **list,
+ int total)
+{
+ struct commit *base = NULL;
+ struct commit **rev;
+ int i = 0, rev_nr = 0;
+
+ base = lookup_commit_reference_by_name(base_commit);
+ if (!base)
+ die(_("Unknown commit %s"), base_commit);
+
+ ALLOC_ARRAY(rev, total);
+ for (i = 0; i < total; i++)
+ rev[i] = list[i];
+
+ rev_nr = total;
+ /*
+ * Get merge base through pair-wise computations
+ * and store it in rev[0].
+ */
+ while (rev_nr > 1) {
+ for (i = 0; i < rev_nr / 2; i++) {
+ struct commit_list *merge_base;
+ merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
+ if (!merge_base || merge_base->next)
+ die(_("Failed to find exact merge base"));
+
+ rev[i] = merge_base->item;
+ }
+
+ if (rev_nr % 2)
+ rev[i] = rev[2 * i];
+ rev_nr = (rev_nr + 1) / 2;
+ }
+
+ if (!in_merge_bases(base, rev[0]))
+ die(_("base commit should be the ancestor of revision list"));
+
+ for (i = 0; i < total; i++) {
+ if (base == list[i])
+ die(_("base commit shouldn't be in revision list"));
+ }
+
+ free(rev);
+ return base;
+}
+
+static void prepare_bases(struct base_tree_info *bases,
+ struct commit *base,
+ struct commit **list,
+ int total)
+{
+ struct commit *commit;
+ struct rev_info revs;
+ struct diff_options diffopt;
+ int i;
+
+ if (!base)
+ return;
+
+ diff_setup(&diffopt);
+ DIFF_OPT_SET(&diffopt, RECURSIVE);
+ diff_setup_done(&diffopt);
+
+ oidcpy(&bases->base_commit, &base->object.oid);
+
+ init_revisions(&revs, NULL);
+ revs.max_parents = 1;
+ revs.topo_order = 1;
+ for (i = 0; i < total; i++) {
+ list[i]->object.flags &= ~UNINTERESTING;
+ add_pending_object(&revs, &list[i]->object, "rev_list");
+ list[i]->util = (void *)1;
+ }
+ base->object.flags |= UNINTERESTING;
+ add_pending_object(&revs, &base->object, "base");
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+ /*
+ * Traverse the commits list, get prerequisite patch ids
+ * and stuff them in bases structure.
+ */
+ while ((commit = get_revision(&revs)) != NULL) {
+ unsigned char sha1[20];
+ struct object_id *patch_id;
+ if (commit->util)
+ continue;
+ if (commit_patch_id(commit, &diffopt, sha1))
+ die(_("cannot get patch id"));
+ ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id);
+ patch_id = bases->patch_id + bases->nr_patch_id;
+ hashcpy(patch_id->hash, sha1);
+ bases->nr_patch_id++;
+ }
+}
+
+static void print_bases(struct base_tree_info *bases)
+{
+ int i;
+
+ /* Only do this once, either for the cover or for the first one */
+ if (is_null_oid(&bases->base_commit))
+ return;
+
+ /* Show the base commit */
+ printf("base-commit: %s\n", oid_to_hex(&bases->base_commit));
+
+ /* Show the prerequisite patches */
+ for (i = bases->nr_patch_id - 1; i >= 0; i--)
+ printf("prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i]));
+
+ free(bases->patch_id);
+ bases->nr_patch_id = 0;
+ bases->alloc_patch_id = 0;
+ oidclr(&bases->base_commit);
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1215,6 +1340,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int reroll_count = -1;
char *branch_name = NULL;
char *from = NULL;
+ char *base_commit = NULL;
+ struct base_tree_info bases;
+
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1277,6 +1405,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, thread_callback },
OPT_STRING(0, "signature", &signature, N_("signature"),
N_("add a signature")),
+ OPT_STRING(0, "base", &base_commit, N_("base-commit"),
+ N_("add prerequisite tree info to the patch series")),
OPT_FILENAME(0, "signature-file", &signature_file,
N_("add a signature from a file")),
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
@@ -1514,6 +1644,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
signature = strbuf_detach(&buf, NULL);
}
+ memset(&bases, 0, sizeof(bases));
+ if (base_commit) {
+ struct commit *base = get_base_commit(base_commit, list, nr);
+ reset_revision_walk();
+ prepare_bases(&bases, base, list, nr);
+ }
+
if (in_reply_to || thread || cover_letter)
rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
if (in_reply_to) {
@@ -1527,6 +1664,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, use_stdout,
origin, nr, list, branch_name, quiet);
+ print_bases(&bases);
total++;
start_number--;
}
@@ -1592,6 +1730,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.mime_boundary);
else
print_signature();
+ print_bases(&bases);
}
if (!use_stdout)
fclose(stdout);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index eed2981..a6ce727 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1460,4 +1460,19 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
test_path_is_dir patchset
'
+test_expect_success 'format-patch --base' '
+ git checkout side &&
+ git format-patch --stdout --base=HEAD~~~ -1 >patch &&
+ grep -e "^base-commit:" -A3 patch >actual &&
+ echo "base-commit: $(git rev-parse HEAD~~~)" >expected &&
+ echo "prerequisite-patch-id: $(git show --patch HEAD~~ | git patch-id --stable | awk "{print \$1}")" >>expected &&
+ echo "prerequisite-patch-id: $(git show --patch HEAD~ | git patch-id --stable | awk "{print \$1}")" >>expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'format-patch --base error handling' '
+ ! git format-patch --base=HEAD~ -2 &&
+ ! git format-patch --base=HEAD~ -3
+'
+
test_done
--
2.8.1.221.ga4c6ba7
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 3/4] format-patch: introduce --base=auto option
2016-04-22 5:42 [PATCH v5 0/4] Add --base option to git-format-patch to record base tree info Xiaolong Ye
2016-04-22 5:42 ` [PATCH v5 1/4] patch-ids: make commit_patch_id() a public helper function Xiaolong Ye
2016-04-22 5:42 ` [PATCH v5 2/4] format-patch: add '--base' option to record base tree info Xiaolong Ye
@ 2016-04-22 5:42 ` Xiaolong Ye
2016-04-22 21:54 ` Junio C Hamano
2016-04-22 5:42 ` [PATCH v5 4/4] format-patch: introduce format.useAutoBase configuration Xiaolong Ye
3 siblings, 1 reply; 12+ messages in thread
From: Xiaolong Ye @ 2016-04-22 5:42 UTC (permalink / raw)
To: git, Junio C Hamano
Cc: fengguang.wu, ying.huang, philip.li, julie.du, Xiaolong Ye
Introduce --base=auto to record the base commit info automatically, the
base_commit will be the merge base of tip commit of the upstream branch
and revision-range specified in cmdline.
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Wu Fengguang <fengguang.wu@intel.com>
Signed-off-by: Xiaolong Ye <xiaolong.ye@intel.com>
---
Documentation/git-format-patch.txt | 6 ++++++
builtin/log.c | 27 ++++++++++++++++++++++++---
t/t4014-format-patch.sh | 15 +++++++++++++++
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1d790f1..bdeecd5 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -575,6 +575,12 @@ You can also use `git format-patch --base=P -3 C` to generate patches
for A, B and C, and the identifiers for P, X, Y, Z are appended at the
end of the first message.
+If set `--base=auto` in cmdline, it will track base commit automatically,
+the base commit will be the merge base of tip commit of the remote-tracking
+branch and revision-range specified in cmdline.
+For a local branch, you need to track a remote branch by `git branch
+--set-upstream-to` before using this option.
+
EXAMPLES
--------
diff --git a/builtin/log.c b/builtin/log.c
index ee332ab..7851d20 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1205,9 +1205,30 @@ static struct commit *get_base_commit(const char *base_commit,
struct commit **rev;
int i = 0, rev_nr = 0;
- base = lookup_commit_reference_by_name(base_commit);
- if (!base)
- die(_("Unknown commit %s"), base_commit);
+ if (!strcmp(base_commit, "auto")) {
+ struct branch *curr_branch = branch_get(NULL);
+ const char *upstream = branch_get_upstream(curr_branch, NULL);
+ if (upstream) {
+ unsigned char sha1[20];
+ if (get_sha1(upstream, sha1))
+ die(_("Failed to resolve '%s' as a valid ref."), upstream);
+ struct commit *commit = lookup_commit_or_die(sha1, "upstream base");
+ struct commit_list *base_list = get_merge_bases_many(commit, total, list);
+ /* There should be one and only one merge base. */
+ if (!base_list || base_list->next)
+ die(_("Could not find exact merge base."));
+ base = base_list->item;
+ free_commit_list(base_list);
+ } else {
+ die(_("Failed to get upstream, if you want to record base commit automatically,\n"
+ "please use git branch --set-upstream-to to track a remote branch.\n"
+ "Or you could specify base commit by --base=<base-commit-id> manually."));
+ }
+ } else {
+ base = lookup_commit_reference_by_name(base_commit);
+ if (!base)
+ die(_("Unknown commit %s"), base_commit);
+ }
ALLOC_ARRAY(rev, total);
for (i = 0; i < total; i++)
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index a6ce727..afcf8b8 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1475,4 +1475,19 @@ test_expect_success 'format-patch --base error handling' '
! git format-patch --base=HEAD~ -3
'
+test_expect_success 'format-patch --base=auto' '
+ git checkout -b new master &&
+ git branch --set-upstream-to=master &&
+ echo "A" >>file &&
+ git add file &&
+ git commit -m "New change #A" &&
+ echo "B" >>file &&
+ git add file &&
+ git commit -m "New change #B" &&
+ git format-patch --stdout --base=auto -2 >patch &&
+ grep -e "^base-commit:" patch >actual &&
+ echo "base-commit: $(git rev-parse master)" >expected &&
+ test_cmp expected actual
+'
+
test_done
--
2.8.1.221.ga4c6ba7
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 4/4] format-patch: introduce format.useAutoBase configuration
2016-04-22 5:42 [PATCH v5 0/4] Add --base option to git-format-patch to record base tree info Xiaolong Ye
` (2 preceding siblings ...)
2016-04-22 5:42 ` [PATCH v5 3/4] format-patch: introduce --base=auto option Xiaolong Ye
@ 2016-04-22 5:42 ` Xiaolong Ye
3 siblings, 0 replies; 12+ messages in thread
From: Xiaolong Ye @ 2016-04-22 5:42 UTC (permalink / raw)
To: git, Junio C Hamano
Cc: fengguang.wu, ying.huang, philip.li, julie.du, Xiaolong Ye
This allows to record the base commit automatically, it is equivalent
to set --base=auto in cmdline.
The format.useAutoBase has lower priority than command line option,
so if user set format.useAutoBase and pass the command line option in
the meantime, base_commit will be the one passed to command line
option.
Signed-off-by: Xiaolong Ye <xiaolong.ye@intel.com>
---
Documentation/config.txt | 5 +++++
builtin/log.c | 17 +++++++++++------
t/t4014-format-patch.sh | 18 ++++++++++++++++++
3 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 42d2b50..1fe2a85 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1259,6 +1259,11 @@ format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory.
+format.useAutoBase::
+ A boolean value which lets you enable the `--base=auto` option of
+ format-patch by default.
+
+
filter.<driver>.clean::
The command which is used to convert the content of a worktree
file to a blob upon checkin. See linkgit:gitattributes[5] for
diff --git a/builtin/log.c b/builtin/log.c
index 7851d20..c3aeef8 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -702,6 +702,7 @@ static void add_header(const char *value)
#define THREAD_DEEP 2
static int thread;
static int do_signoff;
+static int base_auto;
static const char *signature = git_version_string;
static const char *signature_file;
static int config_cover_letter;
@@ -786,6 +787,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "format.outputdirectory"))
return git_config_string(&config_output_directory, var, value);
+ if (!strcmp(var, "format.useautobase")) {
+ base_auto = git_config_bool(var, value);
+ return 0;
+ }
return git_log_config(var, value, cb);
}
@@ -1205,7 +1210,11 @@ static struct commit *get_base_commit(const char *base_commit,
struct commit **rev;
int i = 0, rev_nr = 0;
- if (!strcmp(base_commit, "auto")) {
+ if (base_commit && strcmp(base_commit, "auto")) {
+ base = lookup_commit_reference_by_name(base_commit);
+ if (!base)
+ die(_("Unknown commit %s"), base_commit);
+ } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) {
struct branch *curr_branch = branch_get(NULL);
const char *upstream = branch_get_upstream(curr_branch, NULL);
if (upstream) {
@@ -1224,10 +1233,6 @@ static struct commit *get_base_commit(const char *base_commit,
"please use git branch --set-upstream-to to track a remote branch.\n"
"Or you could specify base commit by --base=<base-commit-id> manually."));
}
- } else {
- base = lookup_commit_reference_by_name(base_commit);
- if (!base)
- die(_("Unknown commit %s"), base_commit);
}
ALLOC_ARRAY(rev, total);
@@ -1666,7 +1671,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
memset(&bases, 0, sizeof(bases));
- if (base_commit) {
+ if (base_commit || base_auto) {
struct commit *base = get_base_commit(base_commit, list, nr);
reset_revision_walk();
prepare_bases(&bases, base, list, nr);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index afcf8b8..dfee0b6 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1490,4 +1490,22 @@ test_expect_success 'format-patch --base=auto' '
test_cmp expected actual
'
+test_expect_success 'format-patch format.base option' '
+ test_when_finished "git config --unset format.useAutoBase" &&
+ git config format.useAutoBase true &&
+ git format-patch --stdout -1 >patch &&
+ grep -e "^base-commit:" patch >actual &&
+ echo "base-commit: $(git rev-parse master)" >expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'format-patch --base overrides format.base' '
+ test_when_finished "git config --unset format.useAutoBase" &&
+ git config format.useAutoBase true &&
+ git format-patch --stdout --base=HEAD~ -1 >patch &&
+ grep -e "^base-commit:" patch >actual &&
+ echo "base-commit: $(git rev-parse HEAD~)" >expected &&
+ test_cmp expected actual
+'
+
test_done
--
2.8.1.221.ga4c6ba7
^ permalink raw reply related [flat|nested] 12+ messages in thread