From: Tejun Heo <tj@kernel.org>
To: Jeff King <peff@peff.net>
Cc: git@vger.kernel.org, Junio C Hamano <jch2355@gmail.com>,
kernel-team@fb.com
Subject: Re: [RFC] cherry-pick notes to find out cherry-picks from the origin
Date: Thu, 6 Dec 2018 14:15:26 -0800 [thread overview]
Message-ID: <20181206221526.GP2509588@devbig004.ftw2.facebook.com> (raw)
In-Reply-To: <20181205161103.GO2509588@devbig004.ftw2.facebook.com>
Hello, Jeff.
So, this is what I currently have. It still does the same thing but a
lot more generic in terms of both interface and implementation.
* All core logics are implemented as core helpers / features.
* Trailer parsing and reverse-mapping in trailer_rev_xrefs_*().
* Note refs which start with xref- (cross-reference) are recognized
by notes core. When notes are added, a dedicated combine_notes
function is used to remove duplicates and curl unreachable
commits. When xref- notes are formatted for printing, it
automatically follows and prints nested xrefs.
* note-cherry-picks is replaced with reverse-trailer-xrefs which can
use other trailers, note refs and tags. --xref-cherry-picks option
makes it use the cherry-pick presets.
Please note that the patch is still a bit rough. I'm polishing and
documenting. Please let me know what you think.
Thanks.
---
Makefile | 1
builtin.h | 1
builtin/reverse-trailer-xrefs.c | 148 ++++++++++++++++++++++++
git.c | 1
notes.c | 245 +++++++++++++++++++++++++++++++++++++++-
notes.h | 10 +
object.c | 4
object.h | 6
trailer.c | 102 ++++++++++++++++
trailer.h | 26 ++++
10 files changed, 540 insertions(+), 4 deletions(-)
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..b2879be6c
--- /dev/null
+++ b/builtin/reverse-trailer-xrefs.c
@@ -0,0 +1,148 @@
+#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 [<options>] [<commit-ish>...]"),
+ NULL
+};
+
+static const char cherry_picked_prefix[] = "(cherry picked from commit ";
+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 *from_commit, struct object_array *to_objs,
+ const char *tag)
+{
+ char from_hex[GIT_MAX_HEXSZ + 1];
+ struct strbuf note = STRBUF_INIT;
+ struct object_id note_oid;
+ int i, ret;
+
+ oid_to_hex_r(from_hex, &from_commit->object.oid);
+
+ for (i = 0; i < to_objs->nr; i++) {
+ const char *hex = to_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", from_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, &from_commit->object.oid, ¬e_oid, NULL);
+ return ret;
+}
+
+int cmd_reverse_trailer_xrefs(int argc, const char **argv, const char *prefix)
+{
+ static struct notes_tree tree;
+ struct rev_info revs;
+ int i, ret;
+ 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 options for xref-cherry-picks notes")),
+ OPT_STRING(0, "trailer-prefix", &trailer_prefix, N_("prefix"), N_("process trailers starting with <prefix>")),
+ OPT_STRING(0, "ref", ¬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);
+
+ init_revisions(&revs, prefix);
+ argc = setup_revisions(argc, argv, &revs, &s_r_opt);
+ argc = parse_options(argc, argv, prefix, options,
+ reverse_trailer_xrefs_usage, 0);
+
+ /* allow inidividual options to override parts of --cherry */
+ if (cherry) {
+ if (!trailer_prefix)
+ trailer_prefix = cherry_picked_prefix;
+ if (!notes_ref)
+ notes_ref = NOTES_CHERRY_PICKS_REF;
+ if (!tag)
+ tag = NOTES_CHERRY_PICKED_TO_TAG;
+ }
+
+ if (!notes_ref || (!clear && (!trailer_prefix || !tag)))
+ die(_("insufficient arguments"));
+
+ if (argc > 1)
+ die(_("unrecognized argument: %s"), argv[1]);
+
+ if (!tree.initialized)
+ 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 *from_commit;
+ struct object_array *to_objs;
+
+ trailer_rev_xrefs_init(&rxrefs, trailer_prefix);
+ traverse_commit_list(&revs, record_trailer_xrefs, NULL, &rxrefs);
+
+ trailer_rev_xrefs_for_each(&rxrefs, i, from_commit, to_objs) {
+ ret = note_trailer_xrefs(&tree, from_commit, to_objs,
+ tag);
+ if (ret)
+ return ret;
+ }
+ }
+
+ commit_notes(&tree, "Notes updated by 'git reverse-trailer-xrefs'");
+
+ return 0;
+}
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 },
diff --git a/notes.c b/notes.c
index 25cdce28b..c32064bfe 100644
--- a/notes.c
+++ b/notes.c
@@ -9,6 +9,7 @@
#include "tree-walk.h"
#include "string-list.h"
#include "refs.h"
+#include "hashmap.h"
/*
* Use a non-balancing simple 16-tree structure with struct int_node as
@@ -79,6 +80,10 @@ static struct notes_tree **display_notes_trees;
static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
struct int_node *node, unsigned int n);
+static void parse_xref_note(const char *note, unsigned long size,
+ const struct object_id *commit_oid,
+ struct object_array *result,
+ struct string_list *result_lines);
/*
* Search the tree until the appropriate location for the given key is found:
@@ -914,6 +919,60 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,
return ret;
}
+int combine_notes_cat_xrefs(struct object_id *cur_oid,
+ const struct object_id *new_oid)
+{
+ char *cur_msg = NULL, *new_msg = NULL;
+ unsigned long cur_len, new_len;
+ enum object_type cur_type, new_type;
+ struct object_array xrefs = OBJECT_ARRAY_INIT;
+ struct string_list lines = STRING_LIST_INIT_DUP;
+ struct strbuf output = STRBUF_INIT;
+ int i, j, cur_nr, ret;
+
+ /* read in both note blob objects */
+ if (!is_null_oid(new_oid))
+ new_msg = read_object_file(new_oid, &new_type, &new_len);
+ if (!new_msg || !new_len || new_type != OBJ_BLOB) {
+ free(new_msg);
+ return 0;
+ }
+ if (!is_null_oid(cur_oid))
+ cur_msg = read_object_file(cur_oid, &cur_type, &cur_len);
+ if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) {
+ free(cur_msg);
+ free(new_msg);
+ oidcpy(cur_oid, new_oid);
+ return 0;
+ }
+
+ /* parse xrefs and de-dup */
+ parse_xref_note(cur_msg, cur_len, NULL, &xrefs, &lines);
+ cur_nr = xrefs.nr;
+ parse_xref_note(new_msg, new_len, NULL, &xrefs, &lines);
+
+ for (i = 0; i < cur_nr; i++)
+ for (j = cur_nr; j < xrefs.nr; j++)
+ if (!strcmp(xrefs.objects[i].name,
+ xrefs.objects[j].name))
+ lines.items[j].string[0] = '\0';
+
+ /* write out the combined object */
+ for (i = 0; i < lines.nr; i++)
+ if (lines.items[i].string[0] != '\0')
+ strbuf_addf(&output, "%s\n", lines.items[i].string);
+
+ ret = write_object_file(output.buf, output.len, blob_type, cur_oid);
+
+ strbuf_release(&output);
+ object_array_clear(&xrefs);
+ string_list_clear(&lines, 0);
+ free(cur_msg);
+ free(new_msg);
+
+ return ret;
+}
+
static int string_list_add_one_ref(const char *refname, const struct object_id *oid,
int flag, void *cb)
{
@@ -996,8 +1055,12 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
if (!notes_ref)
notes_ref = default_notes_ref();
- if (!combine_notes)
- combine_notes = combine_notes_concatenate;
+ if (!combine_notes) {
+ if (starts_with(notes_ref, "refs/notes/xref-"))
+ combine_notes = combine_notes_cat_xrefs;
+ else
+ combine_notes = combine_notes_concatenate;
+ }
t->root = (struct int_node *) xcalloc(1, sizeof(struct int_node));
t->first_non_note = NULL;
@@ -1189,6 +1252,67 @@ void free_notes(struct notes_tree *t)
memset(t, 0, sizeof(struct notes_tree));
}
+/*
+ * Parse a "[TAG:]HEX" line. @xref is trimmed. If @tag_p is not NULL and
+ * TAG exists, the string is split. Returns the pointer to the OID and
+ * *@tag_p is updated to the TAG if requested.
+ */
+static char *parse_xref(char *xref, char **tag_p)
+{
+ char *p, *hex;
+
+ while (isspace(*xref))
+ xref++;
+
+ p = strchr(xref, ':');
+ if (p) {
+ if (tag_p) {
+ *tag_p = xref;
+ *p = '\0';
+ }
+ p++;
+ while (isspace(*p))
+ p++;
+ hex = p;
+ } else {
+ if (tag_p)
+ *tag_p = NULL;
+ hex = xref;
+ }
+
+ p = hex;
+ while (*p != '\0' && !isspace(*p))
+ p++;
+ *p = '\0';
+ return hex;
+}
+
+static void walk_xrefs(const char *tree_ref, struct object_id *from_oid,
+ struct strbuf *sb, int level)
+{
+ struct object_array xrefs = OBJECT_ARRAY_INIT;
+ struct string_list lines = STRING_LIST_INIT_DUP;
+ int i;
+
+ read_xref_note(tree_ref, from_oid, &xrefs, &lines);
+
+ for (i = 0; i < xrefs.nr; i++) {
+ char *line = lines.items[i].string;
+ char *tag;
+
+ parse_xref(line, &tag);
+ strbuf_addf(sb, " %s%s%*s%s\n",
+ tag ?: "", tag ? ": " : "", 2 * level, "",
+ xrefs.objects[i].name);
+ if (xrefs.objects[i].item)
+ walk_xrefs(tree_ref, &xrefs.objects[i].item->oid, sb,
+ level + 1);
+ }
+
+ object_array_clear(&xrefs);
+ string_list_clear(&lines, 0);
+}
+
/*
* Fill the given strbuf with the notes associated with the given object.
*
@@ -1208,6 +1332,7 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid
char *msg, *msg_p;
unsigned long linelen, msglen;
enum object_type type;
+ int format_xrefs;
if (!t)
t = &default_notes_tree;
@@ -1250,6 +1375,8 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid
}
}
+ format_xrefs = !raw && starts_with(t->ref, "refs/notes/xref-");
+
for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
linelen = strchrnul(msg_p, '\n') - msg_p;
@@ -1257,6 +1384,14 @@ static void format_note(struct notes_tree *t, const struct object_id *object_oid
strbuf_addstr(sb, " ");
strbuf_add(sb, msg_p, linelen);
strbuf_addch(sb, '\n');
+
+ if (format_xrefs) {
+ struct object_id oid;
+
+ msg_p[linelen] = '\0';
+ if (!get_oid_hex(parse_xref(msg_p, NULL), &oid))
+ walk_xrefs(t->ref, &oid, sb, 1);
+ }
}
free(msg);
@@ -1309,3 +1444,109 @@ void expand_loose_notes_ref(struct strbuf *sb)
expand_notes_ref(sb);
}
}
+
+struct notes_tree_entry {
+ struct hashmap_entry ent;
+ struct notes_tree tree;
+};
+
+static int notes_tree_cmp(const void *hashmap_cmp_fn_data,
+ const void *entry, const void *entry_or_key,
+ const void *keydata)
+{
+ const struct notes_tree_entry *e1 = entry;
+ const struct notes_tree_entry *e2 = entry_or_key;
+
+ return strcmp(e1->tree.ref, e2->tree.ref);
+}
+
+static void parse_xref_note(const char *note, unsigned long size,
+ const struct object_id *commit_oid,
+ struct object_array *result,
+ struct string_list *result_lines)
+{
+ struct strbuf **lines, **pline;
+
+ lines = strbuf_split_buf(note, size, '\n', 0);
+
+ for (pline = lines; *pline; pline++) {
+ struct strbuf *line = *pline;
+ const char *target_hex;
+ struct object_id target_oid;
+ struct object *target_obj;
+
+ target_hex = parse_xref(line->buf, NULL);
+ if (get_oid_hex(target_hex, &target_oid)) {
+ if (commit_oid)
+ warning("read invalid sha1 on %s: %s",
+ oid_to_hex(commit_oid), line->buf);
+ continue;
+ }
+
+ target_obj = parse_object(the_repository, &target_oid);
+ if (!target_obj || target_obj->type != OBJ_COMMIT) {
+ if (commit_oid)
+ warning("read invalid commit on %s: %s",
+ oid_to_hex(commit_oid), line->buf);
+ continue;
+ }
+
+ add_object_array(target_obj, target_hex, result);
+ if (result_lines) {
+ assert(result_lines->strdup_strings);
+ string_list_append(result_lines, line->buf);
+ }
+ }
+
+ strbuf_list_free(lines);
+}
+
+/*
+ * Read a cross-referencing note.
+ *
+ * Notes in @notes_ref contains lines of "[TAG:]HEX" pointing to other
+ * commits. Read the target commits and add the objects to @result. If
+ * @result_lines is non-NULL, it should point to a strdup'ing string_list.
+ * The verbatim note lines matching the target commits are appened to the
+ * list.
+ */
+void read_xref_note(const char *notes_ref, const struct object_id *commit_oid,
+ struct object_array *result,
+ struct string_list *result_lines)
+{
+ static struct hashmap *notes_tree_map = NULL;
+ unsigned hash = memhash(notes_ref, strlen(notes_ref));
+ struct notes_tree_entry key, *ent;
+ const struct object_id *note_oid;
+ unsigned long size;
+ enum object_type type;
+ char *note;
+
+ if (!notes_tree_map) {
+ notes_tree_map = xcalloc(1, sizeof(struct hashmap));
+ hashmap_init(notes_tree_map, notes_tree_cmp, NULL, 0);
+ }
+
+ hashmap_entry_init(&key.ent, hash);
+ key.tree.ref = (char *)notes_ref;
+ ent = hashmap_get(notes_tree_map, &key, NULL);
+ if (!ent) {
+ ent = xcalloc(1, sizeof(struct notes_tree_entry));
+ init_notes(&ent->tree, notes_ref, NULL, 0);
+ hashmap_entry_init(&ent->ent, hash);
+ hashmap_put(notes_tree_map, ent);
+ }
+
+ note_oid = get_note(&ent->tree, commit_oid);
+ if (!note_oid)
+ return;
+
+ note = read_object_file(note_oid, &type, &size);
+ if (!size) {
+ free(note);
+ return;
+ }
+
+ parse_xref_note(note, size, commit_oid, result, result_lines);
+ free(note);
+}
diff --git a/notes.h b/notes.h
index 414bc6855..fb8153334 100644
--- a/notes.h
+++ b/notes.h
@@ -2,10 +2,14 @@
#define NOTES_H
#include "string-list.h"
+#include "object.h"
struct object_id;
struct strbuf;
+#define NOTES_CHERRY_PICKS_REF "refs/notes/xref-cherry-picks"
+#define NOTES_CHERRY_PICKED_TO_TAG "Cherry-picked-to"
+
/*
* Function type for combining two notes annotating the same object.
*
@@ -38,6 +42,8 @@ int combine_notes_ignore(struct object_id *cur_oid,
const struct object_id *new_oid);
int combine_notes_cat_sort_uniq(struct object_id *cur_oid,
const struct object_id *new_oid);
+int combine_notes_cat_xrefs(struct object_id *cur_oid,
+ const struct object_id *new_oid);
/*
* Notes tree object
@@ -317,4 +323,8 @@ void expand_notes_ref(struct strbuf *sb);
*/
void expand_loose_notes_ref(struct strbuf *sb);
+void read_xref_note(const char *notes_ref, const struct object_id *commit_oid,
+ struct object_array *result,
+ struct string_list *result_lines);
+
#endif
diff --git a/object.c b/object.c
index e54160550..f79652a34 100644
--- a/object.c
+++ b/object.c
@@ -404,7 +404,7 @@ void object_array_clear(struct object_array *array)
/*
* Return true iff array already contains an entry with name.
*/
-static int contains_name(struct object_array *array, const char *name)
+int object_array_contains_name(struct object_array *array, const char *name)
{
unsigned nr = array->nr, i;
struct object_array_entry *object = array->objects;
@@ -422,7 +422,7 @@ void object_array_remove_duplicates(struct object_array *array)
array->nr = 0;
for (src = 0; src < nr; src++) {
- if (!contains_name(array, objects[src].name)) {
+ if (!object_array_contains_name(array, objects[src].name)) {
if (src != array->nr)
objects[array->nr] = objects[src];
array->nr++;
diff --git a/object.h b/object.h
index 796792cb3..a0b3dd312 100644
--- a/object.h
+++ b/object.h
@@ -172,6 +172,12 @@ typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
void object_array_filter(struct object_array *array,
object_array_each_func_t want, void *cb_data);
+/*
+ * Returns 1 if array already contains an entry with the specified name.
+ * Otherwise, 0.
+ */
+int object_array_contains_name(struct object_array *array, const char *name);
+
/*
* Remove from array all but the first entry with a given name.
* Warning: this function uses an O(N^2) algorithm.
diff --git a/trailer.c b/trailer.c
index 0796f326b..3afa38d25 100644
--- a/trailer.c
+++ b/trailer.c
@@ -2,6 +2,7 @@
#include "config.h"
#include "string-list.h"
#include "run-command.h"
+#include "object-store.h"
#include "commit.h"
#include "tempfile.h"
#include "trailer.h"
@@ -1170,3 +1171,104 @@ void format_trailers_from_commit(struct strbuf *out, const char *msg,
format_trailer_info(out, &info, opts);
trailer_info_release(&info);
}
+
+implement_static_commit_slab(trailer_rxrefs_slab, struct object_array *);
+
+static struct object_array *get_trailer_rxrefs(
+ struct trailer_rev_xrefs *rxrefs, struct commit *commit)
+{
+ struct object_array **slot =
+ trailer_rxrefs_slab_peek(&rxrefs->slab, commit);
+
+ return slot ? *slot : NULL;
+}
+
+static struct object_array *get_create_trailer_rxrefs(
+ struct trailer_rev_xrefs *rxrefs, struct commit *commit)
+{
+ struct object_array **slot =
+ trailer_rxrefs_slab_at(&rxrefs->slab, commit);
+
+ if (*slot)
+ return *slot;
+
+ add_object_array(&commit->object, oid_to_hex(&commit->object.oid),
+ &rxrefs->from_commits);
+ *slot = xmalloc(sizeof(struct object_array));
+ **slot = (struct object_array)OBJECT_ARRAY_INIT;
+ return *slot;
+}
+
+void trailer_rev_xrefs_init(struct trailer_rev_xrefs *rxrefs, const char *tag)
+{
+ rxrefs->tag = xstrdup(tag);
+ rxrefs->tag_len = strlen(tag);
+ init_trailer_rxrefs_slab(&rxrefs->slab);
+ rxrefs->from_commits = (struct object_array)OBJECT_ARRAY_INIT;
+}
+
+void trailer_rev_xrefs_record(struct trailer_rev_xrefs *rxrefs,
+ struct commit *commit)
+{
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+ enum object_type type;
+ unsigned long size;
+ void *buffer;
+ struct trailer_info info;
+ int i;
+
+ buffer = read_object_file(&commit->object.oid, &type, &size);
+ trailer_info_get(&info, buffer, &opts);
+
+ /* when nested, the last trailer describes the latest cherry-pick */
+ for (i = info.trailer_nr - 1; i >= 0; i--) {
+ char *line = info.trailers[i];
+
+ if (starts_with(line, rxrefs->tag)) {
+ struct object_id from_oid;
+ struct object *from_object;
+ struct commit *from_commit;
+ struct object_array *to_objs;
+ char cherry_hex[GIT_MAX_HEXSZ + 1];
+
+ if (get_oid_hex(line + rxrefs->tag_len, &from_oid))
+ continue;
+
+ from_object = parse_object(the_repository, &from_oid);
+ if (!from_object || from_object->type != OBJ_COMMIT)
+ continue;
+
+ from_commit = (struct commit *)from_object;
+ to_objs = get_create_trailer_rxrefs(rxrefs, from_commit);
+
+ oid_to_hex_r(cherry_hex, &commit->object.oid);
+ add_object_array(&commit->object, cherry_hex, to_objs);
+ break;
+ }
+ }
+
+ free(buffer);
+}
+
+void trailer_rev_xrefs_release(struct trailer_rev_xrefs *rxrefs)
+{
+ clear_trailer_rxrefs_slab(&rxrefs->slab);
+ object_array_clear(&rxrefs->from_commits);
+ free(rxrefs->tag);
+}
+
+void trailer_rev_xrefs_next(struct trailer_rev_xrefs *rxrefs, int *idx_p,
+ struct commit **from_commit_p,
+ struct object_array **to_objs_p)
+{
+ if (*idx_p >= rxrefs->from_commits.nr) {
+ *from_commit_p = NULL;
+ *to_objs_p = NULL;
+ return;
+ }
+
+ *from_commit_p = (struct commit *)
+ rxrefs->from_commits.objects[*idx_p].item;
+ *to_objs_p = get_trailer_rxrefs(rxrefs, *from_commit_p);
+ (*idx_p)++;
+}
diff --git a/trailer.h b/trailer.h
index b99773964..5a9704e19 100644
--- a/trailer.h
+++ b/trailer.h
@@ -2,6 +2,8 @@
#define TRAILER_H
#include "list.h"
+#include "object.h"
+#include "commit-slab.h"
struct strbuf;
@@ -99,4 +101,28 @@ void trailer_info_release(struct trailer_info *info);
void format_trailers_from_commit(struct strbuf *out, const char *msg,
const struct process_trailer_options *opts);
+declare_commit_slab(trailer_rxrefs_slab, struct object_array *);
+
+struct trailer_rev_xrefs {
+ char *tag;
+ int tag_len;
+ struct trailer_rxrefs_slab slab;
+ struct object_array from_commits;
+};
+
+void trailer_rev_xrefs_init(struct trailer_rev_xrefs *rxrefs, const char *tag);
+void trailer_rev_xrefs_record(struct trailer_rev_xrefs *rxrefs,
+ struct commit *commit);
+void trailer_rev_xrefs_release(struct trailer_rev_xrefs *rxrefs);
+
+void trailer_rev_xrefs_next(struct trailer_rev_xrefs *rxrefs,
+ int *idx_p, struct commit **from_commit_p,
+ struct object_array **to_objs_p);
+
+#define trailer_rev_xrefs_for_each(rxrefs, idx, from_commit, to_objs) \
+ for ((idx) = 0, \
+ trailer_rev_xrefs_next(rxrefs, &(idx), &(from_commit), &(to_objs));\
+ (from_commit); \
+ trailer_rev_xrefs_next(rxrefs, &(idx), &(from_commit), &(to_objs)))
+
#endif /* TRAILER_H */
prev parent reply other threads:[~2018-12-06 22:15 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-10-17 14:39 [RFC] cherry-pick notes to find out cherry-picks from the origin Tejun Heo
2018-10-24 12:24 ` Tejun Heo
2018-11-13 18:00 ` Tejun Heo
2018-11-15 14:40 ` Jeff King
2018-12-05 16:20 ` Tejun Heo
2018-12-06 22:15 ` Tejun Heo [this message]
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=20181206221526.GP2509588@devbig004.ftw2.facebook.com \
--to=tj@kernel.org \
--cc=git@vger.kernel.org \
--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).