notes: allow treeish expressions as notes ref

init_notes() is the main point of entry to the notes API. It ensures that the input can be used as ref, because it needs a ref to update to store notes tree after modifying it. There however are many use cases where notes tree is only read, e.g. "git log --notes=...". Any notes-shaped treeish could be used for such purpose, but it is not allowed due to existing restriction. Allow treeish expressions to be used in the case the notes tree is going to be used without write "permissions". Add a flag to distinguish whether the notes tree is intended to be used read-only, or will be updated. With this change, operations that use notes read-only can be fed any notes-shaped tree-ish can be used, e.g. git log --notes=notes@{1}. Signed-off-by: Mike Hommey <mh@glandium.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Mike Hommey committed Oct 8, 2015 at 11:54 UTC ee76f92fe883305c1260952f5b325b0503311fc9
7 files changed +55 -30
Documentation/pretty-options.txt
+4 -4
@@ -43,7 +43,7 @@ people using 80-column terminals.
43 commit may be copied to the output.
44
45 ifndef::git-rev-list[]
46 ---notes[=<ref>]::
46 +--notes[=<treeish>]::
47 Show the notes (see linkgit:git-notes[1]) that annotate the
48 commit, when showing the commit log message. This is the default
49 for `git log`, `git show` and `git whatchanged` commands when
@@ -54,8 +54,8 @@ By default, the notes shown are from the notes refs listed in the
54 'core.notesRef' and 'notes.displayRef' variables (or corresponding
55 environment overrides). See linkgit:git-config[1] for more details.
56 +
57 -With an optional '<ref>' argument, show this notes ref instead of the
58 -default notes ref(s). The ref specifies the full refname when it begins
57 +With an optional '<treeish>' argument, use the treeish to find the notes
58 +to display. The treeish can specify the full refname when it begins
59 with `refs/notes/`; when it begins with `notes/`, `refs/` and otherwise
60 `refs/notes/` is prefixed to form a full name of the ref.
61 +
@@ -71,7 +71,7 @@ being displayed. Examples: "--notes=foo" will show only notes from
71 "--notes --notes=foo --no-notes --notes=bar" will only show notes
72 from "refs/notes/bar".
73
74 ---show-notes[=<ref>]::
74 +--show-notes[=<treeish>]::
75 --[no-]standard-notes::
76 These options are deprecated. Use the above --notes/--no-notes
77 options instead.
builtin/notes.c
+16 -13
@@ -286,7 +286,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
286 if (!c)
287 return 0;
288 } else {
289 - init_notes(NULL, NULL, NULL, 0);
289 + init_notes(NULL, NULL, NULL, NOTES_INIT_WRITABLE);
290 t = &default_notes_tree;
291 }
292
@@ -329,15 +329,18 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
329 return ret;
330 }
331
332 -static struct notes_tree *init_notes_check(const char *subcommand)
332 +static struct notes_tree *init_notes_check(const char *subcommand,
333 + int flags)
334 {
335 struct notes_tree *t;
335 - init_notes(NULL, NULL, NULL, 0);
336 + const char *ref;
337 + init_notes(NULL, NULL, NULL, flags);
338 t = &default_notes_tree;
339
338 - if (!starts_with(t->ref, "refs/notes/"))
340 + ref = (flags & NOTES_INIT_WRITABLE) ? t->update_ref : t->ref;
341 + if (!starts_with(ref, "refs/notes/"))
342 die("Refusing to %s notes in %s (outside of refs/notes/)",
340 - subcommand, t->ref);
343 + subcommand, ref);
344 return t;
345 }
346
@@ -360,7 +363,7 @@ static int list(int argc, const char **argv, const char *prefix)
363 usage_with_options(git_notes_list_usage, options);
364 }
365
363 - t = init_notes_check("list");
366 + t = init_notes_check("list", 0);
367 if (argc) {
368 if (get_sha1(argv[0], object))
369 die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
@@ -420,7 +423,7 @@ static int add(int argc, const char **argv, const char *prefix)
423 if (get_sha1(object_ref, object))
424 die(_("Failed to resolve '%s' as a valid ref."), object_ref);
425
423 - t = init_notes_check("add");
426 + t = init_notes_check("add", NOTES_INIT_WRITABLE);
427 note = get_note(t, object);
428
429 if (note) {
@@ -511,7 +514,7 @@ static int copy(int argc, const char **argv, const char *prefix)
514 if (get_sha1(object_ref, object))
515 die(_("Failed to resolve '%s' as a valid ref."), object_ref);
516
514 - t = init_notes_check("copy");
517 + t = init_notes_check("copy", NOTES_INIT_WRITABLE);
518 note = get_note(t, object);
519
520 if (note) {
@@ -589,7 +592,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
592 if (get_sha1(object_ref, object))
593 die(_("Failed to resolve '%s' as a valid ref."), object_ref);
594
592 - t = init_notes_check(argv[0]);
595 + t = init_notes_check(argv[0], NOTES_INIT_WRITABLE);
596 note = get_note(t, object);
597
598 prepare_note_data(object, &d, edit ? note : NULL);
@@ -652,7 +655,7 @@ static int show(int argc, const char **argv, const char *prefix)
655 if (get_sha1(object_ref, object))
656 die(_("Failed to resolve '%s' as a valid ref."), object_ref);
657
655 - t = init_notes_check("show");
658 + t = init_notes_check("show", 0);
659 note = get_note(t, object);
660
661 if (!note)
@@ -809,7 +812,7 @@ static int merge(int argc, const char **argv, const char *prefix)
812 expand_notes_ref(&remote_ref);
813 o.remote_ref = remote_ref.buf;
814
812 - t = init_notes_check("merge");
815 + t = init_notes_check("merge", NOTES_INIT_WRITABLE);
816
817 if (strategy) {
818 if (parse_notes_merge_strategy(strategy, &o.strategy)) {
@@ -901,7 +904,7 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
904 argc = parse_options(argc, argv, prefix, options,
905 git_notes_remove_usage, 0);
906
904 - t = init_notes_check("remove");
907 + t = init_notes_check("remove", NOTES_INIT_WRITABLE);
908
909 if (!argc && !from_stdin) {
910 retval = remove_one_note(t, "HEAD", flag);
@@ -943,7 +946,7 @@ static int prune(int argc, const char **argv, const char *prefix)
946 usage_with_options(git_notes_prune_usage, options);
947 }
948
946 - t = init_notes_check("prune");
949 + t = init_notes_check("prune", NOTES_INIT_WRITABLE);
950
951 prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
952 (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
notes-cache.c
+6 -5
@@ -32,14 +32,14 @@ void notes_cache_init(struct notes_cache *c, const char *name,
32 const char *validity)
33 {
34 struct strbuf ref = STRBUF_INIT;
35 - int flags = 0;
35 + int flags = NOTES_INIT_WRITABLE;
36
37 memset(c, 0, sizeof(*c));
38 c->validity = xstrdup(validity);
39
40 strbuf_addf(&ref, "refs/notes/%s", name);
41 if (!notes_cache_match_validity(ref.buf, validity))
42 - flags = NOTES_INIT_EMPTY;
42 + flags |= NOTES_INIT_EMPTY;
43 init_notes(&c->tree, ref.buf, combine_notes_overwrite, flags);
44 strbuf_release(&ref);
45 }
@@ -49,7 +49,8 @@ int notes_cache_write(struct notes_cache *c)
49 unsigned char tree_sha1[20];
50 unsigned char commit_sha1[20];
51
52 - if (!c || !c->tree.initialized || !c->tree.ref || !*c->tree.ref)
52 + if (!c || !c->tree.initialized || !c->tree.update_ref ||
53 + !*c->tree.update_ref)
54 return -1;
55 if (!c->tree.dirty)
56 return 0;
@@ -59,8 +60,8 @@ int notes_cache_write(struct notes_cache *c)
60 if (commit_tree(c->validity, strlen(c->validity), tree_sha1, NULL,
61 commit_sha1, NULL, NULL) < 0)
62 return -1;
62 - if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
63 - 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
63 + if (update_ref("update notes cache", c->tree.update_ref, commit_sha1,
64 + NULL, 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
65 return -1;
66
67 return 0;
notes-utils.c
+3 -3
@@ -37,7 +37,7 @@ void commit_notes(struct notes_tree *t, const char *msg)
37
38 if (!t)
39 t = &default_notes_tree;
40 - if (!t->initialized || !t->ref || !*t->ref)
40 + if (!t->initialized || !t->update_ref || !*t->update_ref)
41 die(_("Cannot commit uninitialized/unreferenced notes tree"));
42 if (!t->dirty)
43 return; /* don't have to commit an unchanged tree */
@@ -48,7 +48,7 @@ void commit_notes(struct notes_tree *t, const char *msg)
48
49 create_notes_commit(t, NULL, buf.buf, buf.len, commit_sha1);
50 strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
51 - update_ref(buf.buf, t->ref, commit_sha1, NULL, 0,
51 + update_ref(buf.buf, t->update_ref, commit_sha1, NULL, 0,
52 UPDATE_REFS_DIE_ON_ERR);
53
54 strbuf_release(&buf);
@@ -148,7 +148,7 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
148 free(c);
149 return NULL;
150 }
151 - c->trees = load_notes_trees(c->refs);
151 + c->trees = load_notes_trees(c->refs, NOTES_INIT_WRITABLE);
152 string_list_clear(c->refs, 0);
153 free(c->refs);
154 return c;
notes.c
+7 -4
@@ -1011,13 +1011,16 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
1011 t->first_non_note = NULL;
1012 t->prev_non_note = NULL;
1013 t->ref = xstrdup_or_null(notes_ref);
1014 + t->update_ref = (flags & NOTES_INIT_WRITABLE) ? t->ref : NULL;
1015 t->combine_notes = combine_notes;
1016 t->initialized = 1;
1017 t->dirty = 0;
1018
1019 if (flags & NOTES_INIT_EMPTY || !notes_ref ||
1019 - read_ref(notes_ref, object_sha1))
1020 + get_sha1_treeish(notes_ref, object_sha1))
1021 return;
1022 + if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, object_sha1))
1023 + die("Cannot use notes ref %s", notes_ref);
1024 if (get_tree_entry(object_sha1, "", sha1, &mode))
1025 die("Failed to read notes tree referenced by %s (%s)",
1026 notes_ref, sha1_to_hex(object_sha1));
@@ -1027,7 +1030,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
1030 load_subtree(t, &root_tree, t->root, 0);
1031 }
1032
1030 -struct notes_tree **load_notes_trees(struct string_list *refs)
1033 +struct notes_tree **load_notes_trees(struct string_list *refs, int flags)
1034 {
1035 struct string_list_item *item;
1036 int counter = 0;
@@ -1035,7 +1038,7 @@ struct notes_tree **load_notes_trees(struct string_list *refs)
1038 trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
1039 for_each_string_list_item(item, refs) {
1040 struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
1038 - init_notes(t, item->string, combine_notes_ignore, 0);
1041 + init_notes(t, item->string, combine_notes_ignore, flags);
1042 trees[counter++] = t;
1043 }
1044 trees[counter] = NULL;
@@ -1071,7 +1074,7 @@ void init_display_notes(struct display_notes_opt *opt)
1074 item->string);
1075 }
1076
1074 - display_notes_trees = load_notes_trees(&display_notes_refs);
1077 + display_notes_trees = load_notes_trees(&display_notes_refs, 0);
1078 string_list_clear(&display_notes_refs, 0);
1079 }
1080
notes.h
+9 -1
@@ -44,6 +44,7 @@ extern struct notes_tree {
44 struct int_node *root;
45 struct non_note *first_non_note, *prev_non_note;
46 char *ref;
47 + char *update_ref;
48 combine_notes_fn combine_notes;
49 int initialized;
50 int dirty;
@@ -71,6 +72,13 @@ const char *default_notes_ref(void);
72 */
73 #define NOTES_INIT_EMPTY 1
74
75 +/*
76 + * By default, the notes tree is only readable, and the notes ref can be
77 + * any treeish. The notes tree can however be made writable with this flag,
78 + * in which case only strict ref names can be used.
79 + */
80 +#define NOTES_INIT_WRITABLE 2
81 +
82 /*
83 * Initialize the given notes_tree with the notes tree structure at the given
84 * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment
@@ -276,7 +284,7 @@ void format_display_notes(const unsigned char *object_sha1,
284 * Load the notes tree from each ref listed in 'refs'. The output is
285 * an array of notes_tree*, terminated by a NULL.
286 */
279 -struct notes_tree **load_notes_trees(struct string_list *refs);
287 +struct notes_tree **load_notes_trees(struct string_list *refs, int flags);
288
289 /*
290 * Add all refs that match 'glob' to the 'list'.
t/t3301-notes.sh
+10
@@ -83,6 +83,16 @@ test_expect_success 'edit existing notes' '
83 test_must_fail git notes show HEAD^
84 '
85
86 +test_expect_success 'show notes from treeish' '
87 + test "b3" = "$(git notes --ref commits^{tree} show)" &&
88 + test "b4" = "$(git notes --ref commits@{1} show)"
89 +'
90 +
91 +test_expect_success 'cannot edit notes from non-ref' '
92 + test_must_fail git notes --ref commits^{tree} edit &&
93 + test_must_fail git notes --ref commits@{1} edit
94 +'
95 +
96 test_expect_success 'cannot "git notes add -m" where notes already exists' '
97 test_must_fail git notes add -m "b2" &&
98 test_path_is_missing .git/NOTES_EDITMSG &&