From 894a9d333e9e2015cad00d95250b7c5d3acea8b6 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Fri, 12 Mar 2010 18:04:26 +0100 Subject: Support showing notes from more than one notes tree With this patch, you can set notes.displayRef to a glob that points at your favourite notes refs, e.g., [notes] displayRef = refs/notes/* Then git-log and friends will show notes from all trees. Thanks to Junio C Hamano for lots of feedback, which greatly influenced the design of the entire series and this commit in particular. Signed-off-by: Thomas Rast Acked-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 162 insertions(+), 7 deletions(-) (limited to 'notes.c') diff --git a/notes.c b/notes.c index 3ba3e6de17..225a16608a 100644 --- a/notes.c +++ b/notes.c @@ -5,6 +5,8 @@ #include "utf8.h" #include "strbuf.h" #include "tree-walk.h" +#include "string-list.h" +#include "refs.h" /* * Use a non-balancing simple 16-tree structure with struct int_node as @@ -68,6 +70,9 @@ struct non_note { struct notes_tree default_notes_tree; +static struct string_list display_notes_refs; +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); @@ -828,6 +833,83 @@ int combine_notes_ignore(unsigned char *cur_sha1, return 0; } +static int string_list_add_one_ref(const char *path, const unsigned char *sha1, + int flag, void *cb) +{ + struct string_list *refs = cb; + if (!unsorted_string_list_has_string(refs, path)) + string_list_append(path, refs); + return 0; +} + +void string_list_add_refs_by_glob(struct string_list *list, const char *glob) +{ + if (has_glob_specials(glob)) { + for_each_glob_ref(string_list_add_one_ref, glob, list); + } else { + unsigned char sha1[20]; + if (get_sha1(glob, sha1)) + warning("notes ref %s is invalid", glob); + if (!unsorted_string_list_has_string(list, glob)) + string_list_append(glob, list); + } +} + +void string_list_add_refs_from_colon_sep(struct string_list *list, + const char *globs) +{ + struct strbuf globbuf = STRBUF_INIT; + struct strbuf **split; + int i; + + strbuf_addstr(&globbuf, globs); + split = strbuf_split(&globbuf, ':'); + + for (i = 0; split[i]; i++) { + if (!split[i]->len) + continue; + if (split[i]->buf[split[i]->len-1] == ':') + strbuf_setlen(split[i], split[i]->len-1); + string_list_add_refs_by_glob(list, split[i]->buf); + } + + strbuf_list_free(split); + strbuf_release(&globbuf); +} + +static int string_list_add_refs_from_list(struct string_list_item *item, + void *cb) +{ + struct string_list *list = cb; + string_list_add_refs_by_glob(list, item->string); + return 0; +} + +static int notes_display_config(const char *k, const char *v, void *cb) +{ + int *load_refs = cb; + + if (*load_refs && !strcmp(k, "notes.displayref")) { + if (!v) + config_error_nonbool(k); + string_list_add_refs_by_glob(&display_notes_refs, v); + } + + return 0; +} + +static const char *default_notes_ref(void) +{ + const char *notes_ref = NULL; + if (!notes_ref) + notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (!notes_ref) + notes_ref = notes_ref_name; /* value of core.notesRef config */ + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + return notes_ref; +} + void init_notes(struct notes_tree *t, const char *notes_ref, combine_notes_fn combine_notes, int flags) { @@ -840,11 +922,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, assert(!t->initialized); if (!notes_ref) - notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT); - if (!notes_ref) - notes_ref = notes_ref_name; /* value of core.notesRef config */ - if (!notes_ref) - notes_ref = GIT_NOTES_DEFAULT_REF; + notes_ref = default_notes_ref(); if (!combine_notes) combine_notes = combine_notes_concatenate; @@ -868,6 +946,63 @@ void init_notes(struct notes_tree *t, const char *notes_ref, load_subtree(t, &root_tree, t->root, 0); } +struct load_notes_cb_data { + int counter; + struct notes_tree **trees; +}; + +static int load_one_display_note_ref(struct string_list_item *item, + void *cb_data) +{ + struct load_notes_cb_data *c = cb_data; + struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, item->string, combine_notes_ignore, 0); + c->trees[c->counter++] = t; + return 0; +} + +struct notes_tree **load_notes_trees(struct string_list *refs) +{ + struct notes_tree **trees; + struct load_notes_cb_data cb_data; + trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *)); + cb_data.counter = 0; + cb_data.trees = trees; + for_each_string_list(load_one_display_note_ref, refs, &cb_data); + trees[cb_data.counter] = NULL; + return trees; +} + +void init_display_notes(struct display_notes_opt *opt) +{ + char *display_ref_env; + int load_config_refs = 0; + display_notes_refs.strdup_strings = 1; + + assert(!display_notes_trees); + + if (!opt || !opt->suppress_default_notes) { + string_list_append(default_notes_ref(), &display_notes_refs); + display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT); + if (display_ref_env) { + string_list_add_refs_from_colon_sep(&display_notes_refs, + display_ref_env); + load_config_refs = 0; + } else + load_config_refs = 1; + } + + git_config(notes_display_config, &load_config_refs); + + if (opt && opt->extra_notes_refs) + for_each_string_list(string_list_add_refs_from_list, + opt->extra_notes_refs, + &display_notes_refs); + + display_notes_trees = load_notes_trees(&display_notes_refs); + string_list_clear(&display_notes_refs, 0); +} + void add_note(struct notes_tree *t, const unsigned char *object_sha1, const unsigned char *note_sha1, combine_notes_fn combine_notes) { @@ -1016,8 +1151,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1, if (msglen && msg[msglen - 1] == '\n') msglen--; - if (flags & NOTES_SHOW_HEADER) - strbuf_addstr(sb, "\nNotes:\n"); + if (flags & NOTES_SHOW_HEADER) { + const char *ref = t->ref; + if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) { + strbuf_addstr(sb, "\nNotes:\n"); + } else { + if (!prefixcmp(ref, "refs/")) + ref += 5; + if (!prefixcmp(ref, "notes/")) + ref += 6; + strbuf_addf(sb, "\nNotes (%s):\n", ref); + } + } for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; @@ -1030,3 +1175,13 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1, free(msg); } + +void format_display_notes(const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags) +{ + int i; + assert(display_notes_trees); + for (i = 0; display_notes_trees[i]; i++) + format_note(display_notes_trees[i], object_sha1, sb, + output_encoding, flags); +} -- cgit 1.2.3-korg From 160baa0d9cbdfcdb6251aa5ede77c59c0d53edfd Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Fri, 12 Mar 2010 18:04:31 +0100 Subject: notes: implement 'git notes copy --stdin' This implements a mass-copy command that takes a sequence of lines in the format SP [ SP ] LF on stdin, and copies each 's notes to the . The is ignored. The intent, of course, is that this can read the same input that the 'post-rewrite' hook gets. The copy_note() function is exposed for everyone's and in particular the next commit's use. Signed-off-by: Thomas Rast Acked-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 12 +++++++++- builtin-notes.c | 56 ++++++++++++++++++++++++++++++++++++++++++++- notes.c | 18 +++++++++++++++ notes.h | 9 ++++++++ t/t3301-notes.sh | 34 +++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) (limited to 'notes.c') diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 7abd0fbd23..6ab3f982b9 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git notes' [list []] 'git notes' add [-f] [-F | -m | (-c | -C) ] [] -'git notes' copy [-f] +'git notes' copy [-f] ( --stdin | ) 'git notes' append [-F | -m | (-c | -C) ] [] 'git notes' edit [] 'git notes' show [] @@ -56,6 +56,16 @@ copy:: objects has none. (use -f to overwrite existing notes to the second object). This subcommand is equivalent to: `git notes add [-f] -C $(git notes list ) ` ++ +In `\--stdin` mode, take lines in the format ++ +---------- + SP [ SP ] LF +---------- ++ +on standard input, and copy the notes from each to its +corresponding . (The optional `` is ignored so that +the command can read the input given to the `post-rewrite` hook.) append:: Append to the notes of an existing object (defaults to HEAD). diff --git a/builtin-notes.c b/builtin-notes.c index 123ecad830..daeb14e1d9 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -278,6 +278,46 @@ int commit_notes(struct notes_tree *t, const char *msg) return 0; } +int notes_copy_from_stdin(int force) +{ + struct strbuf buf = STRBUF_INIT; + struct notes_tree *t; + int ret = 0; + + init_notes(NULL, NULL, NULL, 0); + t = &default_notes_tree; + + while (strbuf_getline(&buf, stdin, '\n') != EOF) { + unsigned char from_obj[20], to_obj[20]; + struct strbuf **split; + int err; + + split = strbuf_split(&buf, ' '); + if (!split[0] || !split[1]) + die("Malformed input line: '%s'.", buf.buf); + strbuf_rtrim(split[0]); + strbuf_rtrim(split[1]); + if (get_sha1(split[0]->buf, from_obj)) + die("Failed to resolve '%s' as a valid ref.", split[0]->buf); + if (get_sha1(split[1]->buf, to_obj)) + die("Failed to resolve '%s' as a valid ref.", split[1]->buf); + + err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite); + + if (err) { + error("Failed to copy notes from '%s' to '%s'", + split[0]->buf, split[1]->buf); + ret = 1; + } + + strbuf_list_free(split); + } + + commit_notes(t, "Notes added by 'git notes copy'"); + free_notes(t); + return ret; +} + int cmd_notes(int argc, const char **argv, const char *prefix) { struct notes_tree *t; @@ -287,7 +327,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) char logmsg[100]; int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0, - remove = 0, prune = 0, force = 0; + remove = 0, prune = 0, force = 0, from_stdin = 0; int given_object = 0, i = 1, retval = 0; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { @@ -301,6 +341,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT", "reuse specified note object", parse_reuse_arg), OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_END() }; @@ -349,8 +390,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); } + if (!copy && from_stdin) { + error("cannot use --stdin with %s subcommand.", argv[0]); + usage_with_options(git_notes_usage, options); + } + if (copy) { const char *from_ref; + if (from_stdin) { + if (argc > 1) { + error("too many parameters"); + usage_with_options(git_notes_usage, options); + } else { + return notes_copy_from_stdin(force); + } + } if (argc < 3) { error("too few parameters"); usage_with_options(git_notes_usage, options); diff --git a/notes.c b/notes.c index 225a16608a..2feeb7bb06 100644 --- a/notes.c +++ b/notes.c @@ -1185,3 +1185,21 @@ void format_display_notes(const unsigned char *object_sha1, format_note(display_notes_trees[i], object_sha1, sb, output_encoding, flags); } + +int copy_note(struct notes_tree *t, + const unsigned char *from_obj, const unsigned char *to_obj, + int force, combine_notes_fn combine_fn) +{ + const unsigned char *note = get_note(t, from_obj); + const unsigned char *existing_note = get_note(t, to_obj); + + if (!force && existing_note) + return 1; + + if (note) + add_note(t, to_obj, note, combine_fn); + else if (existing_note) + add_note(t, to_obj, null_sha1, combine_fn); + + return 0; +} diff --git a/notes.h b/notes.h index 2cc07409db..b7fafb448b 100644 --- a/notes.h +++ b/notes.h @@ -99,6 +99,15 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1); const unsigned char *get_note(struct notes_tree *t, const unsigned char *object_sha1); +/* + * Copy a note from one object to another in the given notes_tree. + * + * Fails if the to_obj already has a note unless 'force' is true. + */ +int copy_note(struct notes_tree *t, + const unsigned char *from_obj, const unsigned char *to_obj, + int force, combine_notes_fn combine_fn); + /* * Flags controlling behaviour of for_each_note() * diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index cb7166f6ec..60ad6a1675 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -776,4 +776,38 @@ test_expect_success 'cannot copy note from object without notes' ' test_must_fail git notes copy HEAD^ HEAD ' +cat > expect << EOF +commit e5d4fb5698d564ab8c73551538ecaf2b0c666185 +Author: A U Thor +Date: Thu Apr 7 15:25:13 2005 -0700 + + 13th + +Notes (other): + yet another note +$whitespace + yet another note + +commit 7038787dfe22a14c3867ce816dbba39845359719 +Author: A U Thor +Date: Thu Apr 7 15:24:13 2005 -0700 + + 12th + +Notes (other): + other note +$whitespace + yet another note +EOF + +test_expect_success 'git notes copy --stdin' ' + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | + git notes copy --stdin && + git log -2 > output && + test_cmp expect output && + test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" && + test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)" +' + test_done -- cgit 1.2.3-korg From 7f710ea98262c7d81006c16c727796d9e6aeaa81 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Fri, 12 Mar 2010 18:04:36 +0100 Subject: notes: track whether notes_trees were changed at all Currently, the notes copying is a bit wasteful since it always creates new trees, even if no notes were copied at all. Teach add_note() and remove_note() to flag the affected notes tree as changed ('dirty'). Then teach builtin/notes.c to use this knowledge and avoid committing trees that weren't changed. Signed-off-by: Thomas Rast Acked-by: Johan Herland Signed-off-by: Junio C Hamano --- builtin-notes.c | 2 ++ notes.c | 3 +++ notes.h | 1 + 3 files changed, 6 insertions(+) (limited to 'notes.c') diff --git a/builtin-notes.c b/builtin-notes.c index 2e45be9de7..e5046b98ed 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -249,6 +249,8 @@ int commit_notes(struct notes_tree *t, const char *msg) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) die("Cannot commit uninitialized/unreferenced notes tree"); + if (!t->dirty) + return 0; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ diff --git a/notes.c b/notes.c index 2feeb7bb06..0261e7898a 100644 --- a/notes.c +++ b/notes.c @@ -933,6 +933,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, t->ref = notes_ref ? xstrdup(notes_ref) : NULL; t->combine_notes = combine_notes; t->initialized = 1; + t->dirty = 0; if (flags & NOTES_INIT_EMPTY || !notes_ref || read_ref(notes_ref, object_sha1)) @@ -1011,6 +1012,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1, if (!t) t = &default_notes_tree; assert(t->initialized); + t->dirty = 1; if (!combine_notes) combine_notes = t->combine_notes; l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); @@ -1026,6 +1028,7 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1) if (!t) t = &default_notes_tree; assert(t->initialized); + t->dirty = 1; hashcpy(l.key_sha1, object_sha1); hashclr(l.val_sha1); return note_tree_remove(t, t->root, 0, &l); diff --git a/notes.h b/notes.h index b7fafb448b..ee65bd1a24 100644 --- a/notes.h +++ b/notes.h @@ -40,6 +40,7 @@ extern struct notes_tree { char *ref; combine_notes_fn *combine_notes; int initialized; + int dirty; } default_notes_tree; /* -- cgit 1.2.3-korg