From: Tejun Heo <tj@kernel.org>
To: git@vger.kernel.org, Junio C Hamano <jch2355@gmail.com>,
Jeff King <peff@peff.net>
Cc: kernel-team@fb.com, Tejun Heo <htejun@fb.com>
Subject: [PATCH 3/5] notes: Implement git-reverse-trailer-xrefs
Date: Tue, 11 Dec 2018 15:49:07 -0800 [thread overview]
Message-ID: <20181211234909.2855638-4-tj@kernel.org> (raw)
In-Reply-To: <20181211234909.2855638-1-tj@kernel.org>
From: Tejun Heo <htejun@fb.com>
Some trailers refer to other commits. Let's call them xrefs
(cross-references). For example, a cherry pick trailer points to the
source commit. It is sometimes useful to build a reverse map of these
xrefs - ie. source -> cherry-pick instead of cherry-pick -> source.
This patch implements git-reverse-trailer-xrefs which can process a
given trailer for the specified commits and generate and update the
matching refs/notes/xref- notes.
The command reverse-maps trailers beginning with --trailer-prefix and
stores them in --ref notes with each line tagged with --tag. It also
has a refs/notes/xref-cherry-picks preset for the three params.
Signed-off-by: Tejun Heo <htejun@fb.com>
---
Documentation/git-reverse-trailer-xrefs.txt | 136 +++++++++++++++++
Makefile | 1 +
builtin.h | 1 +
builtin/reverse-trailer-xrefs.c | 160 ++++++++++++++++++++
git.c | 1 +
5 files changed, 299 insertions(+)
create mode 100644 Documentation/git-reverse-trailer-xrefs.txt
create mode 100644 builtin/reverse-trailer-xrefs.c
diff --git a/Documentation/git-reverse-trailer-xrefs.txt b/Documentation/git-reverse-trailer-xrefs.txt
new file mode 100644
index 000000000..20d260486
--- /dev/null
+++ b/Documentation/git-reverse-trailer-xrefs.txt
@@ -0,0 +1,136 @@
+git-reverse-trailer-xrefs(1)
+============================
+
+NAME
+----
+git-reverse-trailer-xrefs - Record reverse-map of trailer commit references into notes
+
+SYNOPSIS
+--------
+[verse]
+`git reverse_trailer_xrefs` --xref-cherry-picks [--clear] [<options>] [<commit-ish>...]
+`git reverse_trailer_xrefs` --trailer-prefix=<prefix> --ref=<notes-ref> [--tag=<tag>] [<options>] [<commit-ish>...]
+`git reverse_trailer_xrefs` --ref=<notes-ref> --clear [<options>] [<commit-ish>...]
+
+
+DESCRIPTION
+-----------
+Record or clear reverse-map of trailer commit references in the
+specified notes ref.
+
+Some commit trailers reference other commits. For example,
+`git-cherry-pick -x` adds the following trailer to record the source
+commit.
+----------
+(cherry picked from commit <source-commit-id>)
+----------
+The reverse mappings of such cross references can be useful. For
+cherry-picks, it would allow finding all the cherry-picked commits of
+a given source commit. `git-reverse-trailer-xrefs` can be used to
+create and maintain such reverse mappings in notes.
+
+When used with `--xref-cherry-picks`, the cherry-pick trailers are
+parsed from the specified commits and the reverse mappings are
+recorded in the `refs/notes/xref-cherry-picks` notes of the source
+commits in the following format.
+----------
+Cherry-picked-to: <destination-commit-id>
+----------
+
+When a note with its notes ref starting with `refs/notes/xref-` is
+formatted to be displayed with the commit for, e.g., `git-show` or
+`git-log`, the destination commit is followed recursively and the
+matching notes are shown with increasing level of indentations.
+
+`--trailer-prefix`, `--notes` and `--tag` can be used to use a custom
+set of trailer, notes ref and reverse mapping tag.
+
+OPTIONS
+-------
+<commit-ish>...::
+ Commit-ish object names to describe. Defaults to HEAD if omitted.
+
+--xref-cherry-picks::
+ Use the preset to reverse map `git-cherry-pick -x`
+ trailers. `--trailer-prefix` is set to `(cherry-picked from
+ commit `, `--notes` is set to `refs/notes/xref-cherry-picks`
+ and `--tag` is set to `Cherry-picked-to`. This option can't be
+ specified with the three preset options.
+
+--trailer-prefix=<prefix>::
+ Process trailers which start with <prefix>. It is matched
+ character-by-character and should be followed by the
+ referenced commit ID. When there are multiple matching
+ trailers, the last one is used.
+
+--notes=<notes-ref>::
+ The notes ref to use for the reverse mapping. While this can
+ be any notes ref, it is recommented to use ones starting with
+ `refs/notes/xref-` as they are recognized as cross-referencing
+ notes and handled specially when updating and showing.
+
+--tag=<tag>::
+ Optional tag to use when generating reverse reference
+ notes. If specified, each note line is formatted as `<tag>:
+ <commit-id>`; otherwise, `<commit-id>`.
+
+--clear::
+ Instead of creating reverse mapping notes, clear them from the
+ specified commits.
+
+
+EXAMPLES
+--------
+
+Assume the following history. Development is happening in "master" and
+releases are branched off and fixes are cherry-picked into them.
+
+------------
+ D'---E'' release-B
+ /
+ C' E' release-D
+ / /
+A---B---C---D---E master
+------------
+
+The following cherry-picks took place.
+
+------------
+C -> C'
+D -> D'
+E -> E' -> E''
+------------
+
+The reverse mappings for all commits can be created using the
+following command.
+
+------------
+$ git reverse-trailer-xrefs --all --xref-cherry-picks
+------------
+
+With the notes added, where each commit ended up can be easily
+determined.
+
+------------
+$ git log --notes=xref-cherry-picks --oneline | git name-rev --name-only --stdin
+4b165af commit E
+Notes (xref-cherry-picks):
+ Cherry-picked-to: release-D
+ Cherry-picked-to: release-B
+
+82bf1f3 commit D
+Notes (xref-cherry-picks):
+ Cherry-picked-to: release-B~1
+
+364eccf commit C
+Notes (xref-cherry-picks):
+ Cherry-picked-to: release-B~2
+
+ed3adf3 commit B
+ddd1bf2 commit A
+------------
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 1a44c811a..3c23ecf9d 100644
--- a/Makefile
+++ b/Makefile
@@ -1086,6 +1086,7 @@ BUILTIN_OBJS += builtin/multi-pack-index.o
BUILTIN_OBJS += builtin/mv.o
BUILTIN_OBJS += builtin/name-rev.o
BUILTIN_OBJS += builtin/notes.o
+BUILTIN_OBJS += builtin/reverse-trailer-xrefs.o
BUILTIN_OBJS += builtin/pack-objects.o
BUILTIN_OBJS += builtin/pack-redundant.o
BUILTIN_OBJS += builtin/pack-refs.o
diff --git a/builtin.h b/builtin.h
index 6538932e9..51089e258 100644
--- a/builtin.h
+++ b/builtin.h
@@ -195,6 +195,7 @@ extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix)
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_notes(int argc, const char **argv, const char *prefix);
+extern int cmd_reverse_trailer_xrefs(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);
extern int cmd_patch_id(int argc, const char **argv, const char *prefix);
diff --git a/builtin/reverse-trailer-xrefs.c b/builtin/reverse-trailer-xrefs.c
new file mode 100644
index 000000000..0edb3b91a
--- /dev/null
+++ b/builtin/reverse-trailer-xrefs.c
@@ -0,0 +1,160 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "repository.h"
+#include "config.h"
+#include "commit.h"
+#include "blob.h"
+#include "notes.h"
+#include "notes-utils.h"
+#include "trailer.h"
+#include "revision.h"
+#include "list-objects.h"
+#include "object-store.h"
+#include "parse-options.h"
+
+static const char * const reverse_trailer_xrefs_usage[] = {
+ N_("git reverse_trailer_xrefs --xref-cherry-picks [--clear] [<options>] [<commit-ish>...]"),
+ N_("git reverse_trailer_xrefs --trailer-prefix=<prefix> --notes=<notes-ref> --tag=<tag> [<options>] [<commit-ish>...]"),
+ N_("git reverse_trailer_xrefs --notes=<notes-ref> --clear [<options>] [<commit-ish>...]"),
+ NULL
+};
+
+#define CHERRY_PICKED_PREFIX "(cherry picked from commit "
+#define CHERRY_PICKS_REF "refs/notes/xref-cherry-picks"
+#define CHERRY_PICKED_TO_TAG "Cherry-picked-to"
+
+static int verbose;
+
+static void clear_trailer_xref_note(struct commit *commit, void *data)
+{
+ struct notes_tree *tree = data;
+ int status;
+
+ status = remove_note(tree, commit->object.oid.hash);
+
+ if (verbose) {
+ if (status)
+ fprintf(stderr, "Object %s has no note\n",
+ oid_to_hex(&commit->object.oid));
+ else
+ fprintf(stderr, "Removing note for object %s\n",
+ oid_to_hex(&commit->object.oid));
+ }
+}
+
+static void record_trailer_xrefs(struct commit *commit, void *data)
+{
+ trailer_rev_xrefs_record(data, commit);
+}
+
+static int note_trailer_xrefs(struct notes_tree *tree,
+ struct commit *dst_commit,
+ struct object_array *src_objs, const char *tag)
+{
+ char dst_hex[GIT_MAX_HEXSZ + 1];
+ struct strbuf note = STRBUF_INIT;
+ struct object_id note_oid;
+ int i, ret;
+
+ oid_to_hex_r(dst_hex, &dst_commit->object.oid);
+
+ for (i = 0; i < src_objs->nr; i++) {
+ const char *hex = src_objs->objects[i].name;
+
+ if (tag)
+ strbuf_addf(¬e, "%s: %s\n", tag, hex);
+ else
+ strbuf_addf(¬e, "%s\n", tag);
+ if (verbose)
+ fprintf(stderr, "Adding note %s -> %s\n", dst_hex, hex);
+ }
+
+ ret = write_object_file(note.buf, note.len, blob_type, ¬e_oid);
+ strbuf_release(¬e);
+ if (ret)
+ return ret;
+
+ ret = add_note(tree, &dst_commit->object.oid, ¬e_oid, NULL);
+ return ret;
+}
+
+static int reverse_trailer_xrefs(struct rev_info *revs, int clear,
+ const char *trailer_prefix,
+ const char *notes_ref, const char *tag)
+{
+ struct notes_tree tree = { };
+ int i, ret;
+
+ init_notes(&tree, notes_ref, NULL, NOTES_INIT_WRITABLE);
+
+ if (prepare_revision_walk(revs))
+ die("revision walk setup failed");
+
+ if (clear) {
+ traverse_commit_list(revs, clear_trailer_xref_note, NULL, &tree);
+ } else {
+ struct trailer_rev_xrefs rxrefs;
+ struct commit *dst_commit;
+ struct object_array *src_objs;
+
+ trailer_rev_xrefs_init(&rxrefs, trailer_prefix);
+ traverse_commit_list(revs, record_trailer_xrefs, NULL, &rxrefs);
+
+ trailer_rev_xrefs_for_each(&rxrefs, i, dst_commit, src_objs) {
+ ret = note_trailer_xrefs(&tree, dst_commit, src_objs,
+ tag);
+ if (ret)
+ return ret;
+ }
+
+ trailer_rev_xrefs_release(&rxrefs);
+ }
+
+ commit_notes(&tree, "Notes updated by 'git reverse-trailer-xrefs'");
+ return 0;
+}
+
+int cmd_reverse_trailer_xrefs(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct setup_revision_opt s_r_opt = {
+ .def = "HEAD",
+ .revarg_opt = REVARG_CANNOT_BE_FILENAME
+ };
+ int cherry = 0, clear = 0;
+ const char *trailer_prefix = NULL, *notes_ref = NULL, *tag = NULL;
+ struct option options[] = {
+ OPT_BOOL(0, "xref-cherry-picks", &cherry, N_("use preset for xref-cherry-picks notes")),
+ OPT_STRING(0, "trailer-prefix", &trailer_prefix, N_("prefix"), N_("process trailers starting with <prefix>")),
+ OPT_STRING(0, "notes", ¬es_ref, N_("notes-ref"), N_("update notes in <notes-ref>")),
+ OPT_STRING(0, "tag", &tag, N_("tag"), N_("tag xref notes with <tag>")),
+ OPT_BOOL(0, "clear", &clear, N_("clear trailer xref notes from the specified commits")),
+ OPT__VERBOSE(&verbose, N_("verbose")),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options,
+ reverse_trailer_xrefs_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
+
+ init_revisions(&revs, prefix);
+ argc = setup_revisions(argc, argv, &revs, &s_r_opt);
+
+ if (cherry) {
+ trailer_prefix = CHERRY_PICKED_PREFIX;
+ notes_ref = CHERRY_PICKS_REF;
+ tag = CHERRY_PICKED_TO_TAG;
+ }
+
+ if (!notes_ref || (!clear && (!trailer_prefix || !tag)))
+ die(_("insufficient arguments"));
+
+ if (argc > 1)
+ die(_("unrecognized argument: %s"), argv[1]);
+
+ return reverse_trailer_xrefs(&revs, clear,
+ trailer_prefix, notes_ref, tag);
+}
diff --git a/git.c b/git.c
index 2f604a41e..4948c8e01 100644
--- a/git.c
+++ b/git.c
@@ -515,6 +515,7 @@ static struct cmd_struct commands[] = {
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "notes", cmd_notes, RUN_SETUP },
+ { "reverse-trailer-xrefs", cmd_reverse_trailer_xrefs, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
{ "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT },
{ "pack-refs", cmd_pack_refs, RUN_SETUP },
--
2.17.1
next prev parent reply other threads:[~2018-12-11 23:49 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-12-11 23:49 [PATCHSET] git-reverse-trailer-xrefs: Reverse map cherry-picks and other cross-references Tejun Heo
2018-12-11 23:49 ` [PATCH 1/5] trailer: Implement a helper to reverse-map trailer xrefs Tejun Heo
2018-12-11 23:49 ` [PATCH 2/5] notes: Implement special handlings for refs/notes/xref- Tejun Heo
2018-12-11 23:49 ` Tejun Heo [this message]
2018-12-11 23:49 ` [PATCH 4/5] githooks: Add post-cherry-pick and post-fetch hooks Tejun Heo
2018-12-11 23:49 ` [PATCH 5/5] notes: Implement xref-cherry-picks hooks and tests Tejun Heo
2018-12-12 7:26 ` [PATCHSET] git-reverse-trailer-xrefs: Reverse map cherry-picks and other cross-references Junio C Hamano
2018-12-12 14:54 ` Tejun Heo
2018-12-13 3:01 ` Junio C Hamano
2018-12-13 3:09 ` Junio C Hamano
2018-12-13 3:46 ` Tejun Heo
2018-12-18 14:40 ` Stefan Xenos
2018-12-18 16:48 ` Stefan Xenos
2018-12-18 16:51 ` Tejun Heo
2018-12-13 3:40 ` Tejun Heo
2018-12-13 5:47 ` Junio C Hamano
2018-12-13 16:15 ` Tejun Heo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20181211234909.2855638-4-tj@kernel.org \
--to=tj@kernel.org \
--cc=git@vger.kernel.org \
--cc=htejun@fb.com \
--cc=jch2355@gmail.com \
--cc=kernel-team@fb.com \
--cc=peff@peff.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).