{"thread":{"id":"22749","subject":"[PATCH v4 03/11] Documentation: document post-rewrite hook","startedAt":"2010-02-22T00:10:13Z","lastAt":"2010-02-23T00:21:35Z","messageCount":17,"participants":["Thomas Rast","Junio C Hamano"],"isPatch":true,"patchVersion":4,"patchTotal":11},"messages":[{"id":"135253","messageId":"cover.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266703765.git.trast@student.ethz.ch","subject":"[PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:13Z","receivedAt":"2010-02-22T00:10:13Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Thanks Junio for your big batch of feedback.  Yet another round...\n\nSince this topic already has three different goals, I decided to try\nand get it in at least 'pu' shapes to have an agreed-upon basis for\nwhat's next.  To that end, I ejected the filter-branch commits for\nnow, and hopefully addressed all comments on the existing patches.  I\nstill need to make up my mind (and would still appreciate input) on\nthe filter-branch part, but if I'm not mistaken the rest of the series\nhas been on Junio's wishlist.  I also already have a patch to make\nformat-patch fill the comment region from a notes ref that has textual\ndependencies on this, which IIRC has also been mentioned as a wishlist\nitem once or twice.\n\nI used the entire series plus the format-patch feature mentioned above\nfairly heavily during its own development, which unearthed a few more\nbugs.  If there is a comment after the --- line in every patch, it\nworked ;-)\n\nOn Sunday 21 February 2010 04:06:52 Junio C Hamano wrote:\n> If I wrote\n> \n> \t[notes]\n>             displayRef = refs/notes/commit\n>             displayRef = refs/notes/amlog\n> \n> I would expect that the normal notes come first and then notes from the\n> amlog tree, not in the alphabetical \"amlog happens to sort before commit\"\n> order.  [v3 always sorted the displayRef entries for display]\n\nI considered this a feature, but changed it anyway since I don't care\nmuch.  My original rationale was: core.notesRef should be in that list\nimplicitly, and I think the natural position if it is unsorted would\nbe: first.  However, that means that changing core.notesRef or\nGIT_NOTES_REF can shuffle around your notes, which I thought would be\nconfusing.\n\n\nThomas Rast (11):\n  test-lib: unset GIT_NOTES_REF to stop it from influencing tests\n  Support showing notes from more than one notes tree\n  Documentation: document post-rewrite hook\n  commit --amend: invoke post-rewrite hook\n  rebase: invoke post-rewrite hook\n  rebase -i: invoke post-rewrite hook\n  notes: implement 'git notes copy --stdin'\n  notes: implement helpers needed for note copying during rewrite\n  rebase: support automatic notes copying\n  commit --amend: copy notes to the new commit\n  notes: add shorthand --ref to override GIT_NOTES_REF\n\n Documentation/config.txt      |   49 ++++++-\n Documentation/git-notes.txt   |   20 +++-\n Documentation/githooks.txt    |   40 +++++\n builtin-commit.c              |   45 ++++++\n builtin-notes.c               |  195 +++++++++++++++++++++++-\n builtin.h                     |   18 ++\n cache.h                       |    3 +\n git-am.sh                     |   13 ++\n git-rebase--interactive.sh    |   52 ++++++-\n git-rebase.sh                 |    6 +\n notes.c                       |  173 ++++++++++++++++++++-\n notes.h                       |   17 ++\n pretty.c                      |    6 +-\n refs.c                        |    4 +-\n refs.h                        |    5 +\n t/t3301-notes.sh              |  344 ++++++++++++++++++++++++++++++++++++++++-\n t/t3400-rebase.sh             |   17 ++\n t/t3404-rebase-interactive.sh |   24 +++\n t/t5407-post-rewrite-hook.sh  |  172 ++++++++++++++++++++\n t/t7501-commit.sh             |   12 ++\n t/test-lib.sh                 |    4 +\n 21 files changed, 1194 insertions(+), 25 deletions(-)\n create mode 100755 t/t5407-post-rewrite-hook.sh\n"},{"id":"135255","messageId":"a001bcef61b98562dcde481615a733aa2ac44e5a.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:14Z","receivedAt":"2010-02-22T00:10:14Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"---\n\nNew in this round.\n\n t/test-lib.sh |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex afd3053..1e7eab1 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY\n unset GIT_CEILING_DIRECTORIES\n unset SHA1_FILE_DIRECTORIES\n unset SHA1_FILE_DIRECTORY\n+unset GIT_NOTES_REF\n GIT_MERGE_VERBOSITY=5\n export GIT_MERGE_VERBOSITY\n export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135251","messageId":"d51082ad62153e182298242a2e4b7c3c76000a2e.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 02/11] Support showing notes from more than one notes tree","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:15Z","receivedAt":"2010-02-22T00:10:15Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"With this patch, you can set notes.displayRef to a glob that points at\nyour favourite notes refs, e.g.,\n\n[notes]\n\tdisplayRef = refs/notes/*\n\nThen git-log and friends will show notes from all trees.\n\nThanks to Junio C Hamano for lots of feedback, which greatly\ninfluenced the design of the entire series and this commit in\nparticular.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanges since v3:\n\n* introduce GIT_NOTES_DISPLAY_REF\n\n* the notes refs are kept in the order the user provided them\n\n* the header is now 'Notes (refname):' or 'Notes:' in the case of\n  refs/notes/commits\n\n* fixes as per Junio's comments\n\n Documentation/config.txt |   19 ++++++-\n cache.h                  |    1 +\n notes.c                  |  153 ++++++++++++++++++++++++++++++++++++++++++++--\n notes.h                  |    7 ++\n pretty.c                 |    6 +-\n refs.c                   |    4 +-\n refs.h                   |    5 ++\n t/t3301-notes.sh         |  115 ++++++++++++++++++++++++++++++++---\n t/test-lib.sh            |    1 +\n 9 files changed, 290 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 4c36aa9..a0f2b0f 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -517,7 +517,7 @@ core.notesRef::\n \tafter the full SHA-1 of the commit they annotate.\n +\n If such a file exists in the given ref, the referenced blob is read, and\n-appended to the commit message, separated by a \"Notes:\" line.  If the\n+appended to the commit message, separated by a \"Notes from <refname>:\" line.  If the\n given ref itself does not exist, it is not an error, but means that no\n notes should be printed.\n +\n@@ -1300,6 +1300,23 @@ mergetool.keepTemporaries::\n mergetool.prompt::\n \tPrompt before each invocation of the merge resolution program.\n \n+notes.displayRef::\n+\tThe refname in the notes namespace from which to show notes\n+\twhen showing commit messages.  The value of this variable can\n+\tbe set to a glob, in which case notes from all matching refs\n+\twill be shown.  You may also specify this configuration\n+\tvariable several times.  A warning will be issued for refs\n+\tthat do not exist, but a glob that does not match any refs is\n+\tsilently ignored.\n++\n+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`\n+environment variable, which must be a colon (or semicolon) separated\n+list of refs or globs.\n++\n+The effective value of \"core.notesRef\" (possibly overridden by\n+GIT_NOTES_REF) is also implicitly added to the list of refs to be\n+displayed.\n+\n pack.window::\n \tThe size of the window used by linkgit:git-pack-objects[1] when no\n \twindow size is given on the command line. Defaults to 10.\ndiff --git a/cache.h b/cache.h\nindex d478eff..445ce16 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -387,6 +387,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define ATTRIBUTE_MACRO_PREFIX \"[attr]\"\n #define GIT_NOTES_REF_ENVIRONMENT \"GIT_NOTES_REF\"\n #define GIT_NOTES_DEFAULT_REF \"refs/notes/commits\"\n+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT \"GIT_NOTES_DISPLAY_REF\"\n \n extern int is_bare_repository_cfg;\n extern int is_bare_repository(void);\ndiff --git a/notes.c b/notes.c\nindex 3ba3e6d..c480370 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -5,6 +5,8 @@\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"tree-walk.h\"\n+#include \"string-list.h\"\n+#include \"refs.h\"\n \n /*\n  * Use a non-balancing simple 16-tree structure with struct int_node as\n@@ -68,6 +70,9 @@ struct non_note {\n \n struct notes_tree default_notes_tree;\n \n+struct string_list display_notes_refs;\n+struct notes_tree **display_notes_trees;\n+\n static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,\n \t\tstruct int_node *node, unsigned int n);\n \n@@ -828,6 +833,78 @@ int combine_notes_ignore(unsigned char *cur_sha1,\n \treturn 0;\n }\n \n+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,\n+\t\t\t\t   int flag, void *cb)\n+{\n+\tstruct string_list *refs = cb;\n+\tif (!unsorted_string_list_has_string(refs, path))\n+\t\tstring_list_append(path, refs);\n+\treturn 0;\n+}\n+\n+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)\n+{\n+\tif (has_glob_specials(glob)) {\n+\t\tfor_each_glob_ref(string_list_add_one_ref, glob, list);\n+\t} else {\n+\t\tunsigned char sha1[20];\n+\t\tif (get_sha1(glob, sha1))\n+\t\t\twarning(\"notes ref %s is invalid\", glob);\n+\t\tif (!unsorted_string_list_has_string(list, glob))\n+\t\t\tstring_list_append(glob, list);\n+\t}\n+}\n+\n+void string_list_add_refs_from_glob_list(struct string_list *list,\n+\t\t\t\t\t const char *globs)\n+{\n+\tstruct strbuf globbuf = STRBUF_INIT;\n+\tstruct strbuf **split;\n+\tint i;\n+\n+\tstrbuf_addstr(&globbuf, globs);\n+\tsplit = strbuf_split(&globbuf, ':');\n+\n+\tfor (i = 0; split[i]; i++) {\n+\t\tif (!split[i]->len)\n+\t\t\tcontinue;\n+\t\tif (split[i]->buf[split[i]->len-1] == ':')\n+\t\t\tstrbuf_setlen(split[i], split[i]->len-1);\n+\t\tstring_list_add_refs_by_glob(list, split[i]->buf);\n+\t}\n+\n+\tstrbuf_list_free(split);\n+\tstrbuf_release(&globbuf);\n+}\n+\n+static int notes_display_config(const char *k, const char *v, void *cb)\n+{\n+\t/* Warning!  This is currently not executed if\n+\t * GIT_NOTES_DISPLAY_REF is set.  Move the git_config() call\n+\t * outside the test if you add more options. */\n+\n+\tif (!strcmp(k, \"notes.displayref\")) {\n+\t\tif (!v)\n+\t\t\tconfig_error_nonbool(k);\n+\t\tstring_list_add_refs_by_glob(&display_notes_refs, v);\n+\t\treturn 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const char *default_notes_ref()\n+{\n+\tconst char *notes_ref = NULL;\n+\tif (!notes_ref)\n+\t\tnotes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);\n+\tif (!notes_ref)\n+\t\tnotes_ref = notes_ref_name; /* value of core.notesRef config */\n+\tif (!notes_ref)\n+\t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n+\treturn notes_ref;\n+}\n+\n void init_notes(struct notes_tree *t, const char *notes_ref,\n \t\tcombine_notes_fn combine_notes, int flags)\n {\n@@ -840,11 +917,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,\n \tassert(!t->initialized);\n \n \tif (!notes_ref)\n-\t\tnotes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);\n-\tif (!notes_ref)\n-\t\tnotes_ref = notes_ref_name; /* value of core.notesRef config */\n-\tif (!notes_ref)\n-\t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n+\t\tnotes_ref = default_notes_ref();\n \n \tif (!combine_notes)\n \t\tcombine_notes = combine_notes_concatenate;\n@@ -868,6 +941,52 @@ void init_notes(struct notes_tree *t, const char *notes_ref,\n \tload_subtree(t, &root_tree, t->root, 0);\n }\n \n+struct load_notes_cb_data\n+{\n+\tint counter;\n+\tstruct notes_tree **trees;\n+};\n+\n+static int load_one_display_note_ref(struct string_list_item *item,\n+\t\t\t\t     void *cb_data)\n+{\n+\tstruct load_notes_cb_data *c = cb_data;\n+\tstruct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));\n+\tinit_notes(t, item->string, combine_notes_ignore, 0);\n+\tc->trees[c->counter++] = t;\n+\treturn 0;\n+}\n+\n+struct notes_tree **load_notes_trees(struct string_list *refs)\n+{\n+\tstruct notes_tree **trees;\n+\tstruct load_notes_cb_data cb_data;\n+\ttrees = xmalloc((display_notes_refs.nr+1) * sizeof(struct notes_tree *));\n+\tcb_data.counter = 0;\n+\tcb_data.trees = trees;\n+\tfor_each_string_list(load_one_display_note_ref, refs, &cb_data);\n+\ttrees[cb_data.counter] = NULL;\n+\treturn trees;\n+}\n+\n+\n+\n+void init_display_notes()\n+{\n+\tchar *display_ref_env;\n+\tdisplay_notes_refs.strdup_strings = 1;\n+\tstring_list_append(default_notes_ref(), &display_notes_refs);\n+\tdisplay_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);\n+\tif (display_ref_env) {\n+\t\tstring_list_add_refs_from_glob_list(&display_notes_refs,\n+\t\t\t\t\t\t    display_ref_env);\n+\t} else {\n+\t\tgit_config(notes_display_config, NULL);\n+\t}\n+\tdisplay_notes_trees = load_notes_trees(&display_notes_refs);\n+\tstring_list_clear(&display_notes_refs, 0);\n+}\n+\n void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1, combine_notes_fn combine_notes)\n {\n@@ -1016,7 +1135,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n \tif (msglen && msg[msglen - 1] == '\\n')\n \t\tmsglen--;\n \n-\tif (flags & NOTES_SHOW_HEADER)\n+\tif (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {\n+\t\tconst char *ref = t->ref;\n+\t\tif (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {\n+\t\t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n+\t\t} else {\n+\t\t\tif (!prefixcmp(ref, \"refs/\"))\n+\t\t\t\tref += 5;\n+\t\t\tif (!prefixcmp(ref, \"notes/\"))\n+\t\t\t\tref += 6;\n+\t\t\tstrbuf_addf(sb, \"\\nNotes (%s):\\n\", ref);\n+\t\t}\n+\t} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))\n \t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n \n \tfor (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {\n@@ -1030,3 +1160,14 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n \n \tfree(msg);\n }\n+\n+void format_display_notes(const unsigned char *object_sha1,\n+\t\t\t  struct strbuf *sb, const char *output_encoding, int flags)\n+{\n+\tint i;\n+\tif (!display_notes_trees)\n+\t\tinit_display_notes();\n+\tfor (i = 0; display_notes_trees[i]; i++)\n+\t\tformat_note(display_notes_trees[i], object_sha1, sb,\n+\t\t\t    output_encoding, flags);\n+}\ndiff --git a/notes.h b/notes.h\nindex bad03cc..32ae2f0 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -184,6 +184,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n /* Flags controlling how notes are formatted */\n #define NOTES_SHOW_HEADER 1\n #define NOTES_INDENT 2\n+#define NOTES_SHOW_HEADER_WITH_REF 4\n \n /*\n  * Fill the given strbuf with the notes associated with the given object.\n@@ -198,4 +199,10 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n \t\tstruct strbuf *sb, const char *output_encoding, int flags);\n \n+void format_display_notes(const unsigned char *object_sha1,\n+\t\t\t  struct strbuf *sb, const char *output_encoding, int flags);\n+\n+struct notes_tree **load_notes_trees(struct string_list *refs);\n+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);\n+\n #endif\ndiff --git a/pretty.c b/pretty.c\nindex f999485..c185fd2 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t\t}\n \t\treturn 0;\t/* unknown %g placeholder */\n \tcase 'N':\n-\t\tformat_note(NULL, commit->object.sha1, sb,\n+\t\tformat_display_notes(commit->object.sha1, sb,\n \t\t\t    git_log_output_encoding ? git_log_output_encoding\n \t\t\t\t\t\t    : git_commit_encoding, 0);\n \t\treturn 1;\n@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \t\tstrbuf_addch(sb, '\\n');\n \n \tif (context->show_notes)\n-\t\tformat_note(NULL, commit->object.sha1, sb, encoding,\n-\t\t\t    NOTES_SHOW_HEADER | NOTES_INDENT);\n+\t\tformat_display_notes(commit->object.sha1, sb, encoding,\n+\t\t\t\t     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);\n \n \tfree(reencoded);\n }\ndiff --git a/refs.c b/refs.c\nindex 503a8c2..6634091 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n {\n \tstruct strbuf real_pattern = STRBUF_INIT;\n \tstruct ref_filter filter;\n-\tconst char *has_glob_specials;\n \tint ret;\n \n \tif (!prefix && prefixcmp(pattern, \"refs/\"))\n@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,\n \t\tstrbuf_addstr(&real_pattern, prefix);\n \tstrbuf_addstr(&real_pattern, pattern);\n \n-\thas_glob_specials = strpbrk(pattern, \"?*[\");\n-\tif (!has_glob_specials) {\n+\tif (!has_glob_specials(pattern)) {\n \t\t/* Append impiled '/' '*' if not present. */\n \t\tif (real_pattern.buf[real_pattern.len - 1] != '/')\n \t\t\tstrbuf_addch(&real_pattern, '/');\ndiff --git a/refs.h b/refs.h\nindex f7648b9..4a18b08 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -28,6 +28,11 @@ struct ref_lock {\n extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);\n extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);\n \n+static inline const char *has_glob_specials(const char *pattern)\n+{\n+\treturn strpbrk(pattern, \"?*[\");\n+}\n+\n /* can be used to learn about broken ref and symref */\n extern int for_each_rawref(each_ref_fn, void *);\n \ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 90178f9..e5c3de7 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -415,7 +415,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700\n \n     6th\n \n-Notes:\n+Notes (other):\n     other note\n EOF\n \n@@ -448,7 +448,106 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '\n \ttest_cmp expect-not-other output\n '\n \n+cat > expect-both << EOF\n+commit 387a89921c73d7ed72cd94d179c1c7048ca47756\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:18:13 2005 -0700\n+\n+    6th\n+\n+Notes:\n+    order test\n+\n+Notes (other):\n+    other note\n+\n+commit bd1753200303d0a0344be813e504253b3d98e74d\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:17:13 2005 -0700\n+\n+    5th\n+\n+Notes:\n+    replacement for deleted note\n+EOF\n+\n+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '\n+\tGIT_NOTES_REF=refs/notes/commits git notes add \\\n+\t\t-m\"replacement for deleted note\" HEAD^ &&\n+\tGIT_NOTES_REF=refs/notes/commits git notes add -m\"order test\" &&\n+\tgit config --unset core.notesRef &&\n+\tgit config notes.displayRef \"refs/notes/*\" &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect-both output\n+'\n+\n+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '\n+\tgit config core.notesRef refs/notes/commits &&\n+\tgit config notes.displayRef refs/notes/other &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect-both output\n+'\n+\n+test_expect_success 'notes.displayRef can be given more than once' '\n+\tgit config --unset core.notesRef &&\n+\tgit config notes.displayRef refs/notes/commits &&\n+\tgit config --add notes.displayRef refs/notes/other &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect-both output\n+'\n+\n+cat > expect-both-reversed << EOF\n+commit 387a89921c73d7ed72cd94d179c1c7048ca47756\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:18:13 2005 -0700\n+\n+    6th\n+\n+Notes (other):\n+    other note\n+\n+Notes:\n+    order test\n+EOF\n+\n+test_expect_success 'notes.displayRef respects order' '\n+\tgit config core.notesRef refs/notes/other &&\n+\tgit config --unset-all notes.displayRef &&\n+\tgit config notes.displayRef refs/notes/commits &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect-both-reversed output\n+'\n+\n+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '\n+\tgit config --unset-all core.notesRef &&\n+\tgit config --unset-all notes.displayRef &&\n+\tGIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \\\n+\t\tgit log -2 > output &&\n+\ttest_cmp expect-both output\n+'\n+\n+cat > expect-none << EOF\n+commit 387a89921c73d7ed72cd94d179c1c7048ca47756\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:18:13 2005 -0700\n+\n+    6th\n+\n+commit bd1753200303d0a0344be813e504253b3d98e74d\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:17:13 2005 -0700\n+\n+    5th\n+EOF\n+\n+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '\n+\tgit config notes.displayRef \"refs/notes/*\" &&\n+\tGIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&\n+\ttest_cmp expect-none output\n+'\n+\n test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '\n+\tgit config core.notesRef refs/notes/other &&\n \techo \"Note on a tree\" > expect\n \tgit notes add -m \"Note on a tree\" HEAD: &&\n \tgit notes show HEAD: > actual &&\n@@ -472,7 +571,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700\n \n     7th\n \n-Notes:\n+Notes (other):\n     other note\n EOF\n \n@@ -503,7 +602,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700\n \n     9th\n \n-Notes:\n+Notes (other):\n     yet another note\n EOF\n \n@@ -533,7 +632,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700\n \n     9th\n \n-Notes:\n+Notes (other):\n     yet another note\n $whitespace\n     yet another note\n@@ -552,7 +651,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700\n \n     10th\n \n-Notes:\n+Notes (other):\n     other note\n EOF\n \n@@ -569,7 +668,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700\n \n     10th\n \n-Notes:\n+Notes (other):\n     other note\n $whitespace\n     yet another note\n@@ -588,7 +687,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700\n \n     11th\n \n-Notes:\n+Notes (other):\n     other note\n $whitespace\n     yet another note\n@@ -619,7 +718,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700\n \n     11th\n \n-Notes:\n+Notes (other):\n     yet another note\n $whitespace\n     yet another note\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 1e7eab1..c814e9c 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -55,6 +55,7 @@ unset GIT_CEILING_DIRECTORIES\n unset SHA1_FILE_DIRECTORIES\n unset SHA1_FILE_DIRECTORY\n unset GIT_NOTES_REF\n+unset GIT_NOTES_DISPLAY_REF\n GIT_MERGE_VERBOSITY=5\n export GIT_MERGE_VERBOSITY\n export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135248","messageId":"88c06286c4b51fd1ee65a7d323455a7db7689999.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 03/11] Documentation: document post-rewrite hook","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:16Z","receivedAt":"2010-02-22T00:10:16Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This defines the behaviour of the post-rewrite hook support, which\nwill be implemented in the following patches.\n\nWe deliberately do not document how often the hook will be invoked per\nrewriting command, but the interface is designed to keep that at\n\"once\".  This would currently not matter too much, since both rebase\nand filter-branch are shellscripts and spawn many processes anyway.\nHowever, when a fast sequencer in C is implemented, it will be\nbeneficial to only have to run the hook once.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nUnchanged from v3\n\n Documentation/githooks.txt |   36 ++++++++++++++++++++++++++++++++++++\n 1 files changed, 36 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 87e2c03..41895e9 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -317,6 +317,42 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and\n exiting with non-zero status from this script causes the 'git gc --auto'\n to abort.\n \n+post-rewrite\n+~~~~~~~~~~~~\n+\n+This hook is invoked by commands that rewrite commits (`git commit\n+--amend`, 'git-rebase', 'git-filter-branch').  Its first argument\n+denotes the command it was invoked by: currently one of `amend`,\n+`rebase`, or `filter-branch`.  Further command-dependent arguments may\n+be passed in the future.\n+\n+The hook receives a list of the rewritten commits on stdin, in the\n+format\n+\n+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF\n+\n+The 'extra-info' is again command-dependent.  If it is empty, the\n+preceding SP is also omitted.  Currently, no commands pass any\n+'extra-info'.\n+\n+The following command-specific comments apply:\n+\n+rebase::\n+\tFor the 'squash' and 'fixup' operation, all commits that were\n+\tsquashed are listed as being rewritten to the squashed commit.\n+\tThis means that there will be several lines sharing the same\n+\t'new-sha1'.\n+\n+filter-branch::\n+\tCommits that were processed by 'git-filter-branch', but not\n+\tchanged, are not included in the list.  If the list is empty\n+\tafter this filtering, the hook is not invoked at all.\n+\n+There is no default 'post-rewrite' hook, but see the\n+`post-receive-copy-notes` script in `contrib/hooks` for an example\n+that copies your git-notes to the rewritten commits.\n+\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135250","messageId":"81c6f1884ef553fa26ee366fc13690560d376935.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 04/11] commit --amend: invoke post-rewrite hook","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:17Z","receivedAt":"2010-02-22T00:10:17Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The rough structure of run_rewrite_hook() comes from\nrun_receive_hook() in receive-pack.\n\nWe introduce a --no-post-rewrite option and use it to avoid the hook\nwhen called from git-rebase -i 'edit'.  The next patch will add full\nsupport in git-rebase, and we only want to invoke the hook once.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanged since v3: Use head_sha1 without resolving it again to find the\nsame sha1.  I must have been drunk when writing that detour.\n\n builtin-commit.c             |   39 +++++++++++++++++++++++++++++++\n git-rebase--interactive.sh   |    2 +-\n t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 92 insertions(+), 1 deletions(-)\n create mode 100755 t/t5407-post-rewrite-hook.sh\n\ndiff --git a/builtin-commit.c b/builtin-commit.c\nindex 55676fd..f476d85 100644\n--- a/builtin-commit.c\n+++ b/builtin-commit.c\n@@ -66,6 +66,7 @@\n static char *author_name, *author_email, *author_date;\n static int all, edit_flag, also, interactive, only, amend, signoff;\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n+static int no_post_rewrite;\n static char *untracked_files_arg, *force_date;\n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)\n \tOPT_BOOLEAN('z', \"null\", &null_termination,\n \t\t    \"terminate entries with NUL\"),\n \tOPT_BOOLEAN(0, \"amend\", &amend, \"amend previous commit\"),\n+\tOPT_BOOLEAN(0, \"no-post-rewrite\", &no_post_rewrite, \"bypass post-rewrite hook\"),\n \t{ OPTION_STRING, 'u', \"untracked-files\", &untracked_files_arg, \"mode\", \"show untracked files, optional modes: all, normal, no. (Default: all)\", PARSE_OPT_OPTARG, NULL, (intptr_t)\"all\" },\n \tOPT_BOOLEAN(0, \"allow-empty\", &allow_empty, \"ok to record an empty change\"),\n \t/* end commit contents options */\n@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \treturn git_status_config(k, v, s);\n }\n \n+static const char post_rewrite_hook[] = \"hooks/post-rewrite\";\n+\n+static int run_rewrite_hook(const unsigned char *oldsha1,\n+\t\t\t    const unsigned char *newsha1)\n+{\n+\t/* oldsha1 SP newsha1 LF NUL */\n+\tstatic char buf[2*40 + 3];\n+\tstruct child_process proc;\n+\tconst char *argv[3];\n+\tint code;\n+\tsize_t n;\n+\n+\tif (access(git_path(post_rewrite_hook), X_OK) < 0)\n+\t\treturn 0;\n+\n+\targv[0] = git_path(post_rewrite_hook);\n+\targv[1] = \"amend\";\n+\targv[2] = NULL;\n+\n+\tmemset(&proc, 0, sizeof(proc));\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.stdout_to_stderr = 1;\n+\n+\tcode = start_command(&proc);\n+\tif (code)\n+\t\treturn code;\n+\tn = snprintf(buf, sizeof(buf), \"%s %s\\n\",\n+\t\t     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));\n+\twrite_in_full(proc.in, buf, n);\n+\tclose(proc.in);\n+\treturn finish_command(&proc);\n+}\n+\n int cmd_commit(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n@@ -1303,6 +1339,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \n \trerere(0);\n \trun_hook(get_index_file(), \"post-commit\", NULL);\n+\tif (amend && !no_post_rewrite) {\n+\t\trun_rewrite_hook(head_sha1, commit_sha1);\n+\t}\n \tif (!quiet)\n \t\tprint_summary(prefix, commit_sha1);\n \ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 3e4fd14..5735859 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -445,7 +445,7 @@ do_next () {\n \t\tmark_action_done\n \t\tpick_one $sha1 ||\n \t\t\tdie_with_patch $sha1 \"Could not apply $sha1... $rest\"\n-\t\tgit commit --amend\n+\t\tgit commit --amend --no-post-rewrite\n \t\t;;\n \tedit|e)\n \t\tcomment_for_reflog edit\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nnew file mode 100755\nindex 0000000..1020af9\n--- /dev/null\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -0,0 +1,52 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Thomas Rast\n+#\n+\n+test_description='Test the post-rewrite hook.'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit A foo A &&\n+\ttest_commit B foo B &&\n+\ttest_commit C foo C &&\n+\ttest_commit D foo D\n+'\n+\n+mkdir .git/hooks\n+\n+cat >.git/hooks/post-rewrite <<EOF\n+#!/bin/sh\n+echo \\$@ > \"$TRASH_DIRECTORY\"/post-rewrite.args\n+cat > \"$TRASH_DIRECTORY\"/post-rewrite.data\n+EOF\n+chmod u+x .git/hooks/post-rewrite\n+\n+clear_hook_input () {\n+\trm -f post-rewrite.args post-rewrite.data\n+}\n+\n+verify_hook_input () {\n+\ttest_cmp \"$TRASH_DIRECTORY\"/post-rewrite.args expected.args &&\n+\ttest_cmp \"$TRASH_DIRECTORY\"/post-rewrite.data expected.data\n+}\n+\n+test_expect_success 'git commit --amend' '\n+\tclear_hook_input &&\n+\techo \"D new message\" > newmsg &&\n+\toldsha=$(git rev-parse HEAD^0) &&\n+\tgit commit -Fnewmsg --amend &&\n+\techo amend > expected.args &&\n+\techo $oldsha $(git rev-parse HEAD^0) > expected.data &&\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git commit --amend --no-post-rewrite' '\n+\tclear_hook_input &&\n+\techo \"D new message again\" > newmsg &&\n+\tgit commit --no-post-rewrite -Fnewmsg --amend &&\n+\ttest ! -f post-rewrite.args &&\n+\ttest ! -f post-rewrite.data\n+'\n+\n+test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135252","messageId":"407d05a63f09aa006f8d1cd2fa4d1836e25dc9ac.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 05/11] rebase: invoke post-rewrite hook","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:18Z","receivedAt":"2010-02-22T00:10:18Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"We have to deal with two separate code paths: a normal rebase, which\nactually goes through git-am; and rebase {-m|-s}.\n\nThe only small issue with both is that they need to remember the\noriginal sha1 across a possible conflict resolution.  rebase -m\nalready puts this information in $dotest/current, and we just\nintroduce a similar file for git-am.\n\nNote that in git-am, the hook really only runs when coming from\ngit-rebase: the code path that sets the $dotest/original-commit file\nis guarded by a test for $dotest/rebasing.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nUnchanged from v3\n\n git-am.sh                    |   10 ++++++++++\n git-rebase.sh                |    5 +++++\n t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++\n 3 files changed, 45 insertions(+), 0 deletions(-)\n\ndiff --git a/git-am.sh b/git-am.sh\nindex 3c08d53..56428b4 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -575,6 +575,7 @@ do\n \t\t\techo \"Patch is empty.  Was it split wrong?\"\n \t\t\tstop_here $this\n \t\t}\n+\t\trm -f \"$dotest/original-commit\"\n \t\tif test -f \"$dotest/rebasing\" &&\n \t\t\tcommit=$(sed -e 's/^From \\([0-9a-f]*\\) .*/\\1/' \\\n \t\t\t\t-e q \"$dotest/$msgnum\") &&\n@@ -582,6 +583,7 @@ do\n \t\tthen\n \t\t\tgit cat-file commit \"$commit\" |\n \t\t\tsed -e '1,/^$/d' >\"$dotest/msg-clean\"\n+\t\t\techo \"$commit\" > \"$dotest/original-commit\"\n \t\telse\n \t\t\t{\n \t\t\t\tsed -n '/^Subject/ s/Subject: //p' \"$dotest/info\"\n@@ -768,6 +770,10 @@ do\n \tgit update-ref -m \"$GIT_REFLOG_ACTION: $FIRSTLINE\" HEAD $commit $parent ||\n \tstop_here $this\n \n+\tif test -f \"$dotest/original-commit\"; then\n+\t\techo \"$(cat \"$dotest/original-commit\") $commit\" >> \"$dotest/rewritten\"\n+\tfi\n+\n \tif test -x \"$GIT_DIR\"/hooks/post-applypatch\n \tthen\n \t\t\"$GIT_DIR\"/hooks/post-applypatch\n@@ -776,6 +782,10 @@ do\n \tgo_next\n done\n \n+if test -s \"$dotest\"/rewritten && test -x \"$GIT_DIR\"/hooks/post-rewrite; then\n+\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$dotest\"/rewritten\n+fi\n+\n git gc --auto\n \n rm -fr \"$dotest\"\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex fb4fef7..52f8b9b 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -79,6 +79,7 @@ continue_merge () {\n \t\tthen\n \t\t\tprintf \"Committed: %0${prec}d \" $msgnum\n \t\tfi\n+\t\techo \"$cmt $(git rev-parse HEAD^0)\" >> \"$dotest/rewritten\"\n \telse\n \t\tif test -z \"$GIT_QUIET\"\n \t\tthen\n@@ -151,6 +152,10 @@ move_to_original_branch () {\n \n finish_rb_merge () {\n \tmove_to_original_branch\n+\tif test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n+\t\ttest -s \"$dotest\"/rewritten; then\n+\t\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$dotest\"/rewritten\n+\tfi\n \trm -r \"$dotest\"\n \tsay All done.\n }\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 1020af9..1ecaa4b 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '\n \ttest ! -f post-rewrite.data\n '\n \n+test_expect_success 'git rebase' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\ttest_must_fail git rebase --onto A B &&\n+\techo C > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse C) $(git rev-parse HEAD^)\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git rebase --skip' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --skip &&\n+\techo D > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135261","messageId":"e98b078199604cccb22606ae17d0a1f8bebc652a.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 06/11] rebase -i: invoke post-rewrite hook","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:19Z","receivedAt":"2010-02-22T00:10:19Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Aside from the same issue that rebase also has (remembering the\noriginal commit across a conflict resolution), rebase -i brings an\nextra twist: We need to defer writing the rewritten list in the case\nof {squash,fixup} because their rewritten result should be the last\ncommit in the squashed group.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanges since v3: Fixed a bug with the handling of 'edit' that\nrecorded the wrong commit.\n* also handle the work done by skip_unneccessary_picks; not doing so\n  meant that in a simple 'pick;squash' rebase, the 'pick' was not\n  shown as rewritten to the squashed commit\n\n git-rebase--interactive.sh   |   46 +++++++++++++++++++++-\n t/t5407-post-rewrite-hook.sh |   90 ++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 135 insertions(+), 1 deletions(-)\n\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 5735859..564f6ac 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -96,6 +96,13 @@ AUTHOR_SCRIPT=\"$DOTEST\"/author-script\n # command is processed, this file is deleted.\n AMEND=\"$DOTEST\"/amend\n \n+# For the post-rewrite hook, we make a list of rewritten commits and\n+# their new sha1s.  The rewritten-pending list keeps the sha1s of\n+# commits that have been processed, but not committed yet,\n+# e.g. because they are waiting for a 'squash' command.\n+REWRITTEN_LIST=\"$DOTEST\"/rewritten-list\n+REWRITTEN_PENDING=\"$DOTEST\"/rewritten-pending\n+\n PRESERVE_MERGES=\n STRATEGY=\n ONTO=\n@@ -198,6 +205,7 @@ make_patch () {\n }\n \n die_with_patch () {\n+\techo \"$1\" > \"$DOTEST\"/stopped-sha\n \tmake_patch \"$1\"\n \tgit rerere\n \tdie \"$2\"\n@@ -348,6 +356,7 @@ pick_one_preserving_merges () {\n \t\t\t\tprintf \"%s\\n\" \"$msg\" > \"$GIT_DIR\"/MERGE_MSG\n \t\t\t\tdie_with_patch $sha1 \"Error redoing merge $sha1\"\n \t\t\tfi\n+\t\t\techo \"$sha1 $(git rev-parse HEAD^0)\" >> \"$REWRITTEN_LIST\"\n \t\t\t;;\n \t\t*)\n \t\t\toutput git cherry-pick \"$@\" ||\n@@ -425,6 +434,26 @@ die_failed_squash() {\n \tdie_with_patch $1 \"\"\n }\n \n+flush_rewritten_pending() {\n+\ttest -s \"$REWRITTEN_PENDING\" || return\n+\tnewsha1=\"$(git rev-parse HEAD^0)\"\n+\tsed \"s/$/ $newsha1/\" < \"$REWRITTEN_PENDING\" >> \"$REWRITTEN_LIST\"\n+\trm -f \"$REWRITTEN_PENDING\"\n+}\n+\n+record_in_rewritten() {\n+\toldsha1=\"$(git rev-parse $1)\"\n+\techo \"$oldsha1\" >> \"$REWRITTEN_PENDING\"\n+\n+\tcase \"$(peek_next_command)\" in\n+\t    squash|s|fixup|f)\n+\t\t;;\n+\t    *)\n+\t\tflush_rewritten_pending\n+\t\t;;\n+\tesac\n+}\n+\n do_next () {\n \trm -f \"$MSG\" \"$AUTHOR_SCRIPT\" \"$AMEND\" || exit\n \tread command sha1 rest < \"$TODO\"\n@@ -438,6 +467,7 @@ do_next () {\n \t\tmark_action_done\n \t\tpick_one $sha1 ||\n \t\t\tdie_with_patch $sha1 \"Could not apply $sha1... $rest\"\n+\t\trecord_in_rewritten $sha1\n \t\t;;\n \treword|r)\n \t\tcomment_for_reflog reword\n@@ -446,6 +476,7 @@ do_next () {\n \t\tpick_one $sha1 ||\n \t\t\tdie_with_patch $sha1 \"Could not apply $sha1... $rest\"\n \t\tgit commit --amend --no-post-rewrite\n+\t\trecord_in_rewritten $sha1\n \t\t;;\n \tedit|e)\n \t\tcomment_for_reflog edit\n@@ -453,6 +484,7 @@ do_next () {\n \t\tmark_action_done\n \t\tpick_one $sha1 ||\n \t\t\tdie_with_patch $sha1 \"Could not apply $sha1... $rest\"\n+\t\techo \"$1\" > \"$DOTEST\"/stopped-sha\n \t\tmake_patch $sha1\n \t\tgit rev-parse --verify HEAD > \"$AMEND\"\n \t\twarn \"Stopped at $sha1... $rest\"\n@@ -509,6 +541,7 @@ do_next () {\n \t\t\trm -f \"$SQUASH_MSG\" \"$FIXUP_MSG\"\n \t\t\t;;\n \t\tesac\n+\t\trecord_in_rewritten $sha1\n \t\t;;\n \t*)\n \t\twarn \"Unknown command: $command $sha1 $rest\"\n@@ -537,6 +570,11 @@ do_next () {\n \t\ttest ! -f \"$DOTEST\"/verbose ||\n \t\t\tgit diff-tree --stat $(cat \"$DOTEST\"/head)..HEAD\n \t} &&\n+\tif test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n+\t\ttest -s \"$REWRITTEN_LIST\"; then\n+\t\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$REWRITTEN_LIST\"\n+\t\ttrue # we don't care if this hook failed\n+\tfi &&\n \trm -rf \"$DOTEST\" &&\n \tgit gc --auto &&\n \twarn \"Successfully rebased and updated $HEADNAME.\"\n@@ -571,7 +609,12 @@ skip_unnecessary_picks () {\n \t\tesac\n \t\techo \"$command${sha1:+ }$sha1${rest:+ }$rest\" >&$fd\n \tdone <\"$TODO\" >\"$TODO.new\" 3>>\"$DONE\" &&\n-\tmv -f \"$TODO\".new \"$TODO\" ||\n+\tmv -f \"$TODO\".new \"$TODO\" &&\n+\tcase \"$(peek_next_command)\" in\n+\tsquash|s|fixup|f)\n+\t\trecord_in_rewritten \"$ONTO\"\n+\t\t;;\n+\tesac ||\n \tdie \"Could not skip unnecessary pick commands\"\n }\n \n@@ -685,6 +728,7 @@ first and then run 'git rebase --continue' again.\"\n \t\t\t\ttest -n \"$amend\" && git reset --soft $amend\n \t\t\t\tdie \"Could not commit staged changes.\"\n \t\t\t}\n+\t\t\trecord_in_rewritten \"$(cat \"$DOTEST\"/stopped-sha)\"\n \t\tfi\n \n \t\trequire_clean_work_tree\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 1ecaa4b..e1fccc4 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -79,4 +79,94 @@ EOF\n \tverify_hook_input\n '\n \n+test_expect_success 'git rebase -m' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\ttest_must_fail git rebase -m --onto A B &&\n+\techo C > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse C) $(git rev-parse HEAD^)\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git rebase -m --skip' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\ttest_must_fail git rebase --onto A B &&\n+\ttest_must_fail git rebase --skip &&\n+\techo D > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh\n+\n+set_fake_editor\n+\n+test_expect_success 'git rebase -i (unchanged)' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\tFAKE_LINES=\"1 2\" test_must_fail git rebase -i --onto A B &&\n+\techo C > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse C) $(git rev-parse HEAD^)\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git rebase -i (skip)' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\tFAKE_LINES=\"2\" test_must_fail git rebase -i --onto A B &&\n+\techo D > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git rebase -i (squash)' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\tFAKE_LINES=\"1 squash 2\" test_must_fail git rebase -i --onto A B &&\n+\techo C > foo &&\n+\tgit add foo &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse C) $(git rev-parse HEAD)\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n+test_expect_success 'git rebase -i (fixup without conflict)' '\n+\tgit reset --hard D &&\n+\tclear_hook_input &&\n+\tFAKE_LINES=\"1 fixup 2\" git rebase -i B &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<EOF &&\n+$(git rev-parse C) $(git rev-parse HEAD)\n+$(git rev-parse D) $(git rev-parse HEAD)\n+EOF\n+\tverify_hook_input\n+'\n+\n test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135256","messageId":"1999562cd99ed236a13cf17daf47e11f6466dd48.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 07/11] notes: implement 'git notes copy --stdin'","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:20Z","receivedAt":"2010-02-22T00:10:20Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This implements a mass-copy command that takes a sequence of lines in\nthe format\n\n  <from-sha1> SP <to-sha1> [ SP <rest> ] LF\n\non stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The\n<rest> is ignored.  The intent, of course, is that this can read the\nsame input that the 'post-rewrite' hook gets.\n\nThe copy_note() function is exposed for everyone's and in particular\nthe next commit's use.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanged since v3: fixed the strip_then_cmp that I removed in the wrong\ncommit when going from v2 to v3.\n* Fix bug in copy_note() that always removed the note when the source\n  was empty, even for combine=concatenate or such.  A test for this is\n  in the next commit, as with this one we always use combine=overwrite.\n\n Documentation/git-notes.txt |   12 ++++++++-\n builtin-notes.c             |   56 ++++++++++++++++++++++++++++++++++++++++++-\n notes.c                     |   20 +++++++++++++++\n notes.h                     |    9 +++++++\n t/t3301-notes.sh            |   34 ++++++++++++++++++++++++++\n 5 files changed, 129 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 14f73b9..f67cb6a 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n [verse]\n 'git notes' [list [<object>]]\n 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n-'git notes' copy [-f] <from-object> <to-object>\n+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )\n 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [<object>]\n 'git notes' show [<object>]\n@@ -55,6 +55,16 @@ copy::\n \tobjects has none. (use -f to overwrite existing notes to the\n \tsecond object). This subcommand is equivalent to:\n \t`git notes add [-f] -C $(git notes list <from-object>) <to-object>`\n++\n+In `\\--stdin` mode, take lines in the format\n++\n+----------\n+<from-object> SP <to-object> [ SP <rest> ] LF\n+----------\n++\n+on standard input, and copy the notes from each <from-object> to its\n+corresponding <to-object>.  (The optional `<rest>` is ignored so that\n+the command can read the input given to the `post-rewrite` hook.)\n \n append::\n \tAppend to the notes of an existing object (defaults to HEAD).\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 123ecad..daeb14e 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -278,6 +278,46 @@ int commit_notes(struct notes_tree *t, const char *msg)\n \treturn 0;\n }\n \n+int notes_copy_from_stdin(int force)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct notes_tree *t;\n+\tint ret = 0;\n+\n+\tinit_notes(NULL, NULL, NULL, 0);\n+\tt = &default_notes_tree;\n+\n+\twhile (strbuf_getline(&buf, stdin, '\\n') != EOF) {\n+\t\tunsigned char from_obj[20], to_obj[20];\n+\t\tstruct strbuf **split;\n+\t\tint err;\n+\n+\t\tsplit = strbuf_split(&buf, ' ');\n+\t\tif (!split[0] || !split[1])\n+\t\t\tdie(\"Malformed input line: '%s'.\", buf.buf);\n+\t\tstrbuf_rtrim(split[0]);\n+\t\tstrbuf_rtrim(split[1]);\n+\t\tif (get_sha1(split[0]->buf, from_obj))\n+\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", split[0]->buf);\n+\t\tif (get_sha1(split[1]->buf, to_obj))\n+\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", split[1]->buf);\n+\n+\t\terr = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);\n+\n+\t\tif (err) {\n+\t\t\terror(\"Failed to copy notes from '%s' to '%s'\",\n+\t\t\t      split[0]->buf, split[1]->buf);\n+\t\t\tret = 1;\n+\t\t}\n+\n+\t\tstrbuf_list_free(split);\n+\t}\n+\n+\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n+\tfree_notes(t);\n+\treturn ret;\n+}\n+\n int cmd_notes(int argc, const char **argv, const char *prefix)\n {\n \tstruct notes_tree *t;\n@@ -287,7 +327,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tchar logmsg[100];\n \n \tint list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,\n-\t    remove = 0, prune = 0, force = 0;\n+\t    remove = 0, prune = 0, force = 0, from_stdin = 0;\n \tint given_object = 0, i = 1, retval = 0;\n \tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n \tstruct option options[] = {\n@@ -301,6 +341,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK('C', \"reuse-message\", &msg, \"OBJECT\",\n \t\t\t   \"reuse specified note object\", parse_reuse_arg),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n+\t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n \t\tOPT_END()\n \t};\n \n@@ -349,8 +390,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n \n+\tif (!copy && from_stdin) {\n+\t\terror(\"cannot use --stdin with %s subcommand.\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n \tif (copy) {\n \t\tconst char *from_ref;\n+\t\tif (from_stdin) {\n+\t\t\tif (argc > 1) {\n+\t\t\t\terror(\"too many parameters\");\n+\t\t\t\tusage_with_options(git_notes_usage, options);\n+\t\t\t} else {\n+\t\t\t\treturn notes_copy_from_stdin(force);\n+\t\t\t}\n+\t\t}\n \t\tif (argc < 3) {\n \t\t\terror(\"too few parameters\");\n \t\t\tusage_with_options(git_notes_usage, options);\ndiff --git a/notes.c b/notes.c\nindex c480370..d7215e2 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1171,3 +1171,23 @@ void format_display_notes(const unsigned char *object_sha1,\n \t\tformat_note(display_notes_trees[i], object_sha1, sb,\n \t\t\t    output_encoding, flags);\n }\n+\n+int copy_note(struct notes_tree *t,\n+\t      const unsigned char *from_obj, const unsigned char *to_obj,\n+\t      int force, combine_notes_fn combine_fn)\n+{\n+\tconst unsigned char *note = get_note(t, from_obj);\n+\tconst unsigned char *existing_note = get_note(t, to_obj);\n+\n+\tif (!force && existing_note)\n+\t\treturn 1;\n+\n+\tif (note)\n+\t\tadd_note(t, to_obj, note, combine_fn);\n+\telse if (existing_note) {\n+\t\tadd_note(t, to_obj, null_sha1, combine_fn);\n+\t} else\n+\t\tremove_note(t, to_obj);\n+\n+\treturn 0;\n+}\ndiff --git a/notes.h b/notes.h\nindex 32ae2f0..fb09537 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n \t\tconst unsigned char *object_sha1);\n \n /*\n+ * Copy a note from one object to another in the given notes_tree.\n+ *\n+ * Fails if the to_obj already has a note unless 'force' is true.\n+ */\n+int copy_note(struct notes_tree *t,\n+\t      const unsigned char *from_obj, const unsigned char *to_obj,\n+\t      int force, combine_notes_fn combine_fn);\n+\n+/*\n  * Flags controlling behaviour of for_each_note()\n  *\n  * Default behaviour of for_each_note() is to traverse every single note object\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex e5c3de7..40c17f1 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -743,4 +743,38 @@ test_expect_success 'cannot copy note from object without notes' '\n \ttest_must_fail git notes copy HEAD^ HEAD\n '\n \n+cat > expect << EOF\n+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:25:13 2005 -0700\n+\n+    13th\n+\n+Notes (other):\n+    yet another note\n+$whitespace\n+    yet another note\n+\n+commit 7038787dfe22a14c3867ce816dbba39845359719\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:24:13 2005 -0700\n+\n+    12th\n+\n+Notes (other):\n+    other note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'git notes copy --stdin' '\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n+\tgit notes copy --stdin &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect output &&\n+\ttest \"$(git notes list HEAD)\" = \"$(git notes list HEAD~2)\" &&\n+\ttest \"$(git notes list HEAD^)\" = \"$(git notes list HEAD~3)\"\n+'\n+\n test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135258","messageId":"4006267bef22c62d9cf79377d2f24a75ed69799c.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 08/11] notes: implement helpers needed for note copying during rewrite","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:21Z","receivedAt":"2010-02-22T00:10:21Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Implement helper functions to load the rewriting config, and to\nactually copy the notes.  Also document the config.\n\nSecondly, also implement an undocumented --for-rewrite=<cmd> option to\n'git notes copy' which is used like --stdin, but also puts the\nconfiguration for <cmd> into effect.  It will be needed to support the\ncopying in git-rebase.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanges since v3: introduced GIT_NOTES_REWRITE_REF and _MODE.\n* more tests\n\n Documentation/config.txt    |   30 +++++++\n Documentation/git-notes.txt |    4 +\n Documentation/githooks.txt  |    4 +\n builtin-notes.c             |  139 +++++++++++++++++++++++++++++--\n builtin.h                   |   18 ++++\n cache.h                     |    2 +\n notes.h                     |    1 +\n t/t3301-notes.sh            |  195 +++++++++++++++++++++++++++++++++++++++++++\n t/test-lib.sh               |    2 +\n 9 files changed, 387 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex a0f2b0f..46adaf1 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1317,6 +1317,36 @@ The effective value of \"core.notesRef\" (possibly overridden by\n GIT_NOTES_REF) is also implicitly added to the list of refs to be\n displayed.\n \n+notes.rewrite.<command>::\n+\tWhen rewriting commits with <command> (currently `amend` or\n+\t`rebase`) and this variable is set to `true`, git\n+\tautomatically copies your notes from the original to the\n+\trewritten commit.  Defaults to `true`, but see\n+\t\"notes.rewriteRef\" below.\n++\n+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`\n+environment variable, which must be a colon (or semicolon) separated\n+list of refs or globs.\n+\n+notes.rewriteMode::\n+\tWhen copying notes during a rewrite (see the\n+\t\"notes.rewrite.<command>\" option), determines what to do if\n+\tthe target commit already has a note.  Must be one of\n+\t`overwrite`, `concatenate`, or `ignore`.  Defaults to\n+\t`concatenate`.\n++\n+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`\n+environment variable.\n+\n+notes.rewriteRef::\n+\tWhen copying notes during a rewrite, specifies the ref whose\n+\tnotes should be copied.  The ref may be a glob, in which case\n+\tnotes in all matching refs will be copied.  You may also\n+\tspecify this configuration several times.\n++\n+Does not have a default value; you must configure this variable to\n+enable note rewriting.\n+\n pack.window::\n \tThe size of the window used by linkgit:git-pack-objects[1] when no\n \twindow size is given on the command line. Defaults to 10.\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex f67cb6a..92f1249 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -34,6 +34,10 @@ the empty string.  Alternatively, you can set it to a different ref,\n something like \"refs/notes/bugzilla\".  This setting can be overridden\n by the environment variable \"GIT_NOTES_REF\".\n \n+See the description of \"notes.rewrite.<command>\" in\n+linkgit:git-config[1] for a way of carrying your notes across commands\n+that rewrite commits.\n+\n \n SUBCOMMANDS\n -----------\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 41895e9..c33a38e 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the\n preceding SP is also omitted.  Currently, no commands pass any\n 'extra-info'.\n \n+The hook always runs after the automatic note copying (see\n+\"notes.rewrite.<command>\" in linkgit:git-config.txt) has happened, and\n+thus has access to these notes.\n+\n The following command-specific comments apply:\n \n rebase::\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex daeb14e..8c65709 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -16,6 +16,7 @@\n #include \"exec_cmd.h\"\n #include \"run-command.h\"\n #include \"parse-options.h\"\n+#include \"string-list.h\"\n \n static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n@@ -278,14 +279,121 @@ int commit_notes(struct notes_tree *t, const char *msg)\n \treturn 0;\n }\n \n-int notes_copy_from_stdin(int force)\n+\n+combine_notes_fn *parse_combine_notes_fn(const char *v)\n+{\n+\tif (!strcasecmp(v, \"overwrite\"))\n+\t\treturn combine_notes_overwrite;\n+\telse if (!strcasecmp(v, \"ignore\"))\n+\t\treturn combine_notes_ignore;\n+\telse if (!strcasecmp(v, \"concatenate\"))\n+\t\treturn combine_notes_concatenate;\n+\telse\n+\t\treturn NULL;\n+}\n+\n+static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+{\n+\tstruct notes_rewrite_cfg *c = cb;\n+\tif (!prefixcmp(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\n+\t\tc->enabled = git_config_bool(k, v);\n+\t\treturn 0;\n+\t} else if (!c->mode_from_env && !strcmp(k, \"notes.rewritemode\")) {\n+\t\tif (!v)\n+\t\t\tconfig_error_nonbool(k);\n+\t\tc->combine = parse_combine_notes_fn(v);\n+\t\tif (!c->combine) {\n+\t\t\terror(\"Bad notes.rewriteMode value: '%s'\", v);\n+\t\t\treturn 1;\n+\t\t}\n+\t\treturn 0;\n+\t} else if (!c->refs_from_env && !strcmp(k, \"notes.rewriteref\")) {\n+\t\t/* note that a refs/ prefix is implied in the\n+\t\t * underlying for_each_glob_ref */\n+\t\tif (!prefixcmp(v, \"refs/notes/\") || !prefixcmp(v, \"notes/\"))\n+\t\t\tstring_list_add_refs_by_glob(c->refs, v);\n+\t\telse\n+\t\t\twarning(\"Refusing to rewrite notes in %s\"\n+\t\t\t\t\" (outside of refs/notes/)\", v);\n+\t\treturn 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+\n+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)\n+{\n+\tstruct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));\n+\tconst char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);\n+\tconst char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);\n+\tc->cmd = cmd;\n+\tc->enabled = 1;\n+\tc->combine = combine_notes_concatenate;\n+\tc->refs = xcalloc(1, sizeof(struct string_list));\n+\tc->refs->strdup_strings = 1;\n+\tc->refs_from_env = 0;\n+\tc->mode_from_env = 0;\n+\tif (rewrite_mode_env) {\n+\t\tc->mode_from_env = 1;\n+\t\tc->combine = parse_combine_notes_fn(rewrite_mode_env);\n+\t\tif (!c->combine)\n+\t\t\terror(\"Bad \" GIT_NOTES_REWRITE_MODE_ENVIRONMENT\n+\t\t\t      \" value: '%s'\", rewrite_mode_env);\n+\t}\n+\tif (rewrite_refs_env) {\n+\t\tc->refs_from_env = 1;\n+\t\tstring_list_add_refs_from_glob_list(c->refs, rewrite_refs_env);\n+\t}\n+\tgit_config(notes_rewrite_config, c);\n+\tif (!c->enabled || !c->refs->nr) {\n+\t\tstring_list_clear(c->refs, 0);\n+\t\tfree(c->refs);\n+\t\tfree(c);\n+\t\treturn NULL;\n+\t}\n+\tc->trees = load_notes_trees(c->refs);\n+\tstring_list_clear(c->refs, 0);\n+\tfree(c->refs);\n+\treturn c;\n+}\n+\n+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,\n+\t\t\t  const unsigned char *from_obj, const unsigned char *to_obj)\n+{\n+\tint ret = 0;\n+\tint i;\n+\tfor (i = 0; c->trees[i]; i++)\n+\t\tret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;\n+\treturn ret;\n+}\n+\n+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)\n+{\n+\tint i;\n+\tfor (i = 0; c->trees[i]; i++) {\n+\t\tcommit_notes(c->trees[i], \"Notes added by 'git notes copy'\");\n+\t\tfree_notes(c->trees[i]);\n+\t}\n+\tfree(c->trees);\n+\tfree(c);\n+}\n+\n+int notes_copy_from_stdin(int force, const char *rewrite_cmd)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct notes_rewrite_cfg *c = NULL;\n \tstruct notes_tree *t;\n \tint ret = 0;\n \n-\tinit_notes(NULL, NULL, NULL, 0);\n-\tt = &default_notes_tree;\n+\tif (rewrite_cmd) {\n+\t\tc = init_copy_notes_for_rewrite(rewrite_cmd);\n+\t\tif (!c)\n+\t\t\treturn 0;\n+\t} else {\n+\t\tinit_notes(NULL, NULL, NULL, 0);\n+\t\tt = &default_notes_tree;\n+\t}\n \n \twhile (strbuf_getline(&buf, stdin, '\\n') != EOF) {\n \t\tunsigned char from_obj[20], to_obj[20];\n@@ -302,7 +410,11 @@ int notes_copy_from_stdin(int force)\n \t\tif (get_sha1(split[1]->buf, to_obj))\n \t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", split[1]->buf);\n \n-\t\terr = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);\n+\t\tif (rewrite_cmd)\n+\t\t\terr = copy_note_for_rewrite(c, from_obj, to_obj);\n+\t\telse\n+\t\t\terr = copy_note(t, from_obj, to_obj, force,\n+\t\t\t\t\tcombine_notes_overwrite);\n \n \t\tif (err) {\n \t\t\terror(\"Failed to copy notes from '%s' to '%s'\",\n@@ -313,8 +425,12 @@ int notes_copy_from_stdin(int force)\n \t\tstrbuf_list_free(split);\n \t}\n \n-\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n-\tfree_notes(t);\n+\tif (!rewrite_cmd) {\n+\t\tcommit_notes(t, \"Notes added by 'git notes copy'\");\n+\t\tfree_notes(t);\n+\t} else {\n+\t\tfinish_copy_notes_for_rewrite(c);\n+\t}\n \treturn ret;\n }\n \n@@ -330,6 +446,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t    remove = 0, prune = 0, force = 0, from_stdin = 0;\n \tint given_object = 0, i = 1, retval = 0;\n \tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n+\tconst char *rewrite_cmd = NULL;\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes options\"),\n \t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n@@ -342,6 +459,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\t\t   \"reuse specified note object\", parse_reuse_arg),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n \t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n+\t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n+\t\t\t   \"load rewriting config for <command> (implies --stdin)\"),\n \t\tOPT_END()\n \t};\n \n@@ -390,6 +509,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n \n+\tif (!copy && rewrite_cmd) {\n+\t\terror(\"cannot use --for-rewrite with %s subcommand.\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n \tif (!copy && from_stdin) {\n \t\terror(\"cannot use --stdin with %s subcommand.\", argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n@@ -397,12 +520,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tif (copy) {\n \t\tconst char *from_ref;\n-\t\tif (from_stdin) {\n+\t\tif (from_stdin || rewrite_cmd) {\n \t\t\tif (argc > 1) {\n \t\t\t\terror(\"too many parameters\");\n \t\t\t\tusage_with_options(git_notes_usage, options);\n \t\t\t} else {\n-\t\t\t\treturn notes_copy_from_stdin(force);\n+\t\t\t\treturn notes_copy_from_stdin(force, rewrite_cmd);\n \t\t\t}\n \t\t}\n \t\tif (argc < 3) {\ndiff --git a/builtin.h b/builtin.h\nindex cdf9847..8aebe61 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -20,6 +20,24 @@ extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n \t\tconst char *author);\n extern int commit_notes(struct notes_tree *t, const char *msg);\n+\n+struct notes_rewrite_cfg\n+{\n+\tstruct notes_tree **trees;\n+\tconst char *cmd;\n+\tint enabled;\n+\tcombine_notes_fn *combine;\n+\tstruct string_list *refs;\n+\tint refs_from_env;\n+\tint mode_from_env;\n+};\n+\n+combine_notes_fn *parse_combine_notes_fn(const char *v);\n+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);\n+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,\n+\t\t\t  const unsigned char *from_obj, const unsigned char *to_obj);\n+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);\n+\n extern int check_pager_config(const char *cmd);\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\ndiff --git a/cache.h b/cache.h\nindex 445ce16..ab740e3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -388,6 +388,8 @@ static inline enum object_type object_type(unsigned int mode)\n #define GIT_NOTES_REF_ENVIRONMENT \"GIT_NOTES_REF\"\n #define GIT_NOTES_DEFAULT_REF \"refs/notes/commits\"\n #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT \"GIT_NOTES_DISPLAY_REF\"\n+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT \"GIT_NOTES_REWRITE_REF\"\n+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT \"GIT_NOTES_REWRITE_MODE\"\n \n extern int is_bare_repository_cfg;\n extern int is_bare_repository(void);\ndiff --git a/notes.h b/notes.h\nindex fb09537..1f48af3 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -213,5 +213,6 @@ void format_display_notes(const unsigned char *object_sha1,\n \n struct notes_tree **load_notes_trees(struct string_list *refs);\n void string_list_add_refs_by_glob(struct string_list *list, const char *glob);\n+void string_list_add_refs_from_glob_list(struct string_list *list, const char *globs);\n \n #endif\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 40c17f1..34a993c 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -777,4 +777,199 @@ test_expect_success 'git notes copy --stdin' '\n \ttest \"$(git notes list HEAD^)\" = \"$(git notes list HEAD~3)\"\n '\n \n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:26:13 2005 -0700\n+\n+    14th\n+EOF\n+\n+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '\n+\ttest_commit 14th &&\n+\ttest_commit 15th &&\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    yet another note\n+$whitespace\n+    yet another note\n+\n+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:26:13 2005 -0700\n+\n+    14th\n+\n+Notes (other):\n+    other note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'git notes copy --for-rewrite (enabled)' '\n+\tgit config notes.rewriteMode overwrite &&\n+\tgit config notes.rewriteRef \"refs/notes/*\" &&\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'git notes copy --for-rewrite (disabled)' '\n+\tgit config notes.rewrite.bar false &&\n+\techo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |\n+\tgit notes copy --for-rewrite=bar &&\n+\tgit log -2 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    a fresh note\n+EOF\n+\n+test_expect_success 'git notes copy --for-rewrite (overwrite)' '\n+\tgit notes add -f -m\"a fresh note\" HEAD^ &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'git notes copy --for-rewrite (ignore)' '\n+\tgit config notes.rewriteMode ignore &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    a fresh note\n+    another fresh note\n+EOF\n+\n+test_expect_success 'git notes copy --for-rewrite (append)' '\n+\tgit notes add -f -m\"another fresh note\" HEAD^ &&\n+\tgit config notes.rewriteMode concatenate &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    a fresh note\n+    another fresh note\n+    append 1\n+    append 2\n+EOF\n+\n+test_expect_success 'git notes copy --for-rewrite (append two to one)' '\n+\tgit notes add -f -m\"append 1\" HEAD^ &&\n+\tgit notes add -f -m\"append 2\" HEAD^^ &&\n+\t(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);\n+\techo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'git notes copy --for-rewrite (append empty)' '\n+\tgit notes remove HEAD^ &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    replacement note 1\n+EOF\n+\n+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '\n+\tgit notes add -f -m\"replacement note 1\" HEAD^ &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tGIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:27:13 2005 -0700\n+\n+    15th\n+\n+Notes (other):\n+    replacement note 2\n+EOF\n+\n+test_expect_success 'GIT_NOTES_REWRITE_REF works' '\n+\tgit config notes.rewriteMode overwrite &&\n+\tgit notes add -f -m\"replacement note 2\" HEAD^ &&\n+\tgit config --unset-all notes.rewriteRef &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tGIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \\\n+\t\tgit notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '\n+\tgit config notes.rewriteRef refs/notes/other &&\n+\tgit notes add -f -m\"replacement note 3\" HEAD^ &&\n+\techo $(git rev-parse HEAD^) $(git rev-parse HEAD) |\n+\tGIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n test_done\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex c814e9c..4caad9f 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES\n unset SHA1_FILE_DIRECTORY\n unset GIT_NOTES_REF\n unset GIT_NOTES_DISPLAY_REF\n+unset GIT_NOTES_REWRITE_REF\n+unset GIT_NOTES_REWRITE_MODE\n GIT_MERGE_VERBOSITY=5\n export GIT_MERGE_VERBOSITY\n export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135257","messageId":"57ca9ec9bc9ccdc05c3e54667e7e27dfc9e18ebe.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 09/11] rebase: support automatic notes copying","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:22Z","receivedAt":"2010-02-22T00:10:22Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Luckily, all the support already happens to be there.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanged from v3: more tests\n\n git-am.sh                     |    5 ++++-\n git-rebase--interactive.sh    |    4 ++++\n git-rebase.sh                 |    1 +\n t/t3400-rebase.sh             |   17 +++++++++++++++++\n t/t3404-rebase-interactive.sh |   24 ++++++++++++++++++++++++\n 5 files changed, 50 insertions(+), 1 deletions(-)\n\ndiff --git a/git-am.sh b/git-am.sh\nindex 56428b4..83cf6e7 100755\n--- a/git-am.sh\n+++ b/git-am.sh\n@@ -782,8 +782,11 @@ do\n \tgo_next\n done\n \n-if test -s \"$dotest\"/rewritten && test -x \"$GIT_DIR\"/hooks/post-rewrite; then\n+if test -s \"$dotest\"/rewritten; then\n+    git notes copy --for-rewrite=rebase < \"$dotest\"/rewritten\n+    if test -x \"$GIT_DIR\"/hooks/post-rewrite; then\n \t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$dotest\"/rewritten\n+    fi\n fi\n \n git gc --auto\ndiff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh\nindex 564f6ac..415ae72 100755\n--- a/git-rebase--interactive.sh\n+++ b/git-rebase--interactive.sh\n@@ -570,6 +570,10 @@ do_next () {\n \t\ttest ! -f \"$DOTEST\"/verbose ||\n \t\t\tgit diff-tree --stat $(cat \"$DOTEST\"/head)..HEAD\n \t} &&\n+\t{\n+\t\tgit notes copy --for-rewrite=rebase < \"$REWRITTEN_LIST\" ||\n+\t\ttrue # we don't care if this copying failed\n+\t} &&\n \tif test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n \t\ttest -s \"$REWRITTEN_LIST\"; then\n \t\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$REWRITTEN_LIST\"\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 52f8b9b..e0eb956 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -152,6 +152,7 @@ move_to_original_branch () {\n \n finish_rb_merge () {\n \tmove_to_original_branch\n+\tgit notes copy --for-rewrite=rebase < \"$dotest\"/rewritten\n \tif test -x \"$GIT_DIR\"/hooks/post-rewrite &&\n \t\ttest -s \"$dotest\"/rewritten; then\n \t\t\"$GIT_DIR\"/hooks/post-rewrite rebase < \"$dotest\"/rewritten\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 4314ad2..dbf7dfb 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \tgit diff --exit-code file-with-cr:CR HEAD:CR\n '\n \n+test_expect_success 'rebase can copy notes' '\n+\tgit config notes.rewrite.rebase true &&\n+\tgit config notes.rewriteRef \"refs/notes/*\" &&\n+\ttest_commit n1 &&\n+\ttest_commit n2 &&\n+\ttest_commit n3 &&\n+\tgit notes add -m\"a note\" n3 &&\n+\tgit rebase --onto n1 n2 &&\n+\ttest \"a note\" = \"$(git notes show HEAD)\"\n+'\n+\n+test_expect_success 'rebase -m can copy notes' '\n+\tgit reset --hard n3 &&\n+\tgit rebase -m --onto n1 n2 &&\n+\ttest \"a note\" = \"$(git notes show HEAD)\"\n+'\n+\n test_done\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 4e35137..19668c2 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -553,4 +553,28 @@ test_expect_success 'reword' '\n \tgit show HEAD~2 | grep \"C changed\"\n '\n \n+test_expect_success 'rebase -i can copy notes' '\n+\tgit config notes.rewrite.rebase true &&\n+\tgit config notes.rewriteRef \"refs/notes/*\" &&\n+\ttest_commit n1 &&\n+\ttest_commit n2 &&\n+\ttest_commit n3 &&\n+\tgit notes add -m\"a note\" n3 &&\n+\tgit rebase --onto n1 n2 &&\n+\ttest \"a note\" = \"$(git notes show HEAD)\"\n+'\n+\n+cat >expect <<EOF\n+an earlier note\n+a note\n+EOF\n+\n+test_expect_success 'rebase -i can copy notes over a fixup' '\n+\tgit reset --hard n3 &&\n+\tgit notes add -m\"an earlier note\" n2 &&\n+\tGIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES=\"1 fixup 2\" git rebase -i n1 &&\n+\tgit notes show > output &&\n+\ttest_cmp expect output\n+'\n+\n test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135259","messageId":"1847c7beec59d9069eba758eaf67649a0629a15f.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 10/11] commit --amend: copy notes to the new commit","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:23Z","receivedAt":"2010-02-22T00:10:23Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Teaches 'git commit --amend' to copy notes.  The catch is that this\nmust also be guarded by --no-post-rewrite, which we use to prevent\n--amend from copying notes during a rebase -i 'edit'/'reword'.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nUnchanged from v3\n\n builtin-commit.c  |    6 ++++++\n t/t7501-commit.sh |   12 ++++++++++++\n 2 files changed, 18 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin-commit.c b/builtin-commit.c\nindex f476d85..ccc4f92 100644\n--- a/builtin-commit.c\n+++ b/builtin-commit.c\n@@ -1340,6 +1340,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \trerere(0);\n \trun_hook(get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n+\t\tstruct notes_rewrite_cfg *cfg;\n+\t\tcfg = init_copy_notes_for_rewrite(\"amend\");\n+\t\tif (cfg) {\n+\t\t\tcopy_note_for_rewrite(cfg, head_sha1, commit_sha1);\n+\t\t\tfinish_copy_notes_for_rewrite(cfg);\n+\t\t}\n \t\trun_rewrite_hook(head_sha1, commit_sha1);\n \t}\n \tif (!quiet)\ndiff --git a/t/t7501-commit.sh b/t/t7501-commit.sh\nindex 7940901..8297cb4 100755\n--- a/t/t7501-commit.sh\n+++ b/t/t7501-commit.sh\n@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '\n \n '\n \n+test_expect_success 'amend can copy notes' '\n+\n+\tgit config notes.rewrite.amend true &&\n+\tgit config notes.rewriteRef \"refs/notes/*\" &&\n+\ttest_commit foo &&\n+\tgit notes add -m\"a note\" &&\n+\ttest_tick &&\n+\tgit commit --amend -m\"new foo\" &&\n+\ttest \"$(git notes show)\" = \"a note\"\n+\n+'\n+\n test_done\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135260","messageId":"4517b10981339e33231966f44e1063848ecfda0e.1266797028.git.trast@student.ethz.ch","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"[PATCH v4 11/11] notes: add shorthand --ref to override GIT_NOTES_REF","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:10:24Z","receivedAt":"2010-02-22T00:10:24Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Adds a shorthand option that overrides the GIT_NOTES_REF variable, and\nhence determines the notes tree that will be manipulated.  It also\nDWIMs a refs/notes/ prefix.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n\nChanged since v3: refs/notes/ is now implied, instead of erroring out\nif it is not there.\n\n Documentation/git-notes.txt |    4 ++++\n builtin-notes.c             |   16 ++++++++++++++++\n 2 files changed, 20 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 92f1249..dcdcc95 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -115,6 +115,10 @@ OPTIONS\n \tLike '-C', but with '-c' the editor is invoked, so that\n \tthe user can further edit the note message.\n \n+--ref <ref>::\n+\tManipulate the notes tree in <ref>.  This overrides both\n+\tGIT_NOTES_REF and the \"core.notesRef\" configuration.\n+\n Author\n ------\n Written by Johannes Schindelin <johannes.schindelin@gmx.de> and\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 8c65709..0300efa 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -447,6 +447,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tint given_object = 0, i = 1, retval = 0;\n \tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n \tconst char *rewrite_cmd = NULL;\n+\tconst char *override_notes_ref = NULL;\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes options\"),\n \t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n@@ -459,6 +460,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\t\t   \"reuse specified note object\", parse_reuse_arg),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n \t\tOPT_BOOLEAN(0, \"stdin\", &from_stdin, \"read objects from stdin\"),\n+\t\tOPT_STRING(0, \"ref\", &override_notes_ref, \"notes_ref\",\n+\t\t\t   \"use notes from <notes_ref>\"),\n \t\tOPT_STRING(0, \"for-rewrite\", &rewrite_cmd, \"command\",\n \t\t\t   \"load rewriting config for <command> (implies --stdin)\"),\n \t\tOPT_END()\n@@ -468,6 +471,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \targc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);\n \n+\tif (override_notes_ref) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tif (!prefixcmp(override_notes_ref, \"refs/notes/\"))\n+\t\t\t/* we're happy */;\n+\t\telse if (!prefixcmp(override_notes_ref, \"notes/\"))\n+\t\t\tstrbuf_addstr(&sb, \"refs/\");\n+\t\telse\n+\t\t\tstrbuf_addstr(&sb, \"refs/notes/\");\n+\t\tstrbuf_addstr(&sb, override_notes_ref);\n+\t\tsetenv(\"GIT_NOTES_REF\", sb.buf, 1);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n \tif (argc && !strcmp(argv[0], \"list\"))\n \t\tlist = 1;\n \telse if (argc && !strcmp(argv[0], \"add\"))\n-- \n1.7.0.147.g5aeb9\n"},{"id":"135262","messageId":"7vr5oeazhd.fsf@alter.siamese.dyndns.org","threadId":"22749","inReplyTo":"cover.1266797028.git.trast@student.ethz.ch","subject":"Re: [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-02-22T00:25:02Z","receivedAt":"2010-02-22T00:25:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> core.notesRef should be in that list\n> implicitly, and I think the natural position if it is unsorted would\n> be: first.\n\nI think that is a sane thing to do, and then any additional ones can come\nafter that first one, in the order specified by displayRef.  Isn't that\nwhat you are doing, no?\n"},{"id":"135264","messageId":"201002220132.53455.trast@student.ethz.ch","threadId":"22749","inReplyTo":"7vr5oeazhd.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T00:32:53Z","receivedAt":"2010-02-22T00:32:53Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"On Monday 22 February 2010 01:25:02 Junio C Hamano wrote:\n> Thomas Rast <trast@student.ethz.ch> writes:\n> \n> > core.notesRef should be in that list\n> > implicitly, and I think the natural position if it is unsorted would\n> > be: first.\n> \n> I think that is a sane thing to do, and then any additional ones can come\n> after that first one, in the order specified by displayRef.  Isn't that\n> what you are doing, no?\n\nYes, I just wanted to spell out why I originally decided for the\nsorted instead of user-ordered way:\n\n> > However, that means that changing core.notesRef or GIT_NOTES_REF\n> > can shuffle around your notes, which I thought would be confusing.\n\nBut that wasn't a strong preference at all, so doing it like you\nsuggested (and like the code is in v4) is fine by me.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"135364","messageId":"7v1vgc26zd.fsf@alter.siamese.dyndns.org","threadId":"22749","inReplyTo":"d51082ad62153e182298242a2e4b7c3c76000a2e.1266797028.git.trast@student.ethz.ch","subject":"Re: [PATCH v4 02/11] Support showing notes from more than one notes tree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-02-22T23:20:06Z","receivedAt":"2010-02-22T23:20:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 4c36aa9..a0f2b0f 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -517,7 +517,7 @@ core.notesRef::\n>  \tafter the full SHA-1 of the commit they annotate.\n>  +\n>  If such a file exists in the given ref, the referenced blob is read, and\n> -appended to the commit message, separated by a \"Notes:\" line.  If the\n> +appended to the commit message, separated by a \"Notes from <refname>:\" line.  If the\n\nHmm...\n\n> diff --git a/notes.c b/notes.c\n> index 3ba3e6d..c480370 100644\n> --- a/notes.c\n> +++ b/notes.c\n> @@ -68,6 +70,9 @@ struct non_note {\n>  \n>  struct notes_tree default_notes_tree;\n>  \n> +struct string_list display_notes_refs;\n> +struct notes_tree **display_notes_trees;\n\nDo these need to be extern?\n\n> +void string_list_add_refs_from_glob_list(struct string_list *list,\n> +\t\t\t\t\t const char *globs)\n> +{\n> +\tstruct strbuf globbuf = STRBUF_INIT;\n> +\tstruct strbuf **split;\n> +\tint i;\n> +\n> +\tstrbuf_addstr(&globbuf, globs);\n> +\tsplit = strbuf_split(&globbuf, ':');\n> +\n> +\tfor (i = 0; split[i]; i++) {\n> +\t\tif (!split[i]->len)\n> +\t\t\tcontinue;\n> +\t\tif (split[i]->buf[split[i]->len-1] == ':')\n> +\t\t\tstrbuf_setlen(split[i], split[i]->len-1);\n> +\t\tstring_list_add_refs_by_glob(list, split[i]->buf);\n> +\t}\n\nNice use of strbuf_split().  I wonder if we should automatically add\n\"refs/\" and/or \"refs/notes/\" when the input is missing the prefix.  I\ndon't have strong preference myself but the users might make noises.\n\nEither way it needs to be documented in the final version before the\nseries goes to 'master'.\n\n> +static int notes_display_config(const char *k, const char *v, void *cb)\n> +{\n> +\t/* Warning!  This is currently not executed if\n> +\t * GIT_NOTES_DISPLAY_REF is set.  Move the git_config() call\n> +\t * outside the test if you add more options. */\n\nYuck.  If you know what needs to be done, do that before other poeple add\nmore options, please.\n\n> @@ -1016,7 +1135,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n>  \tif (msglen && msg[msglen - 1] == '\\n')\n>  \t\tmsglen--;\n>  \n> -\tif (flags & NOTES_SHOW_HEADER)\n> +\tif (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {\n> +\t\tconst char *ref = t->ref;\n> +\t\tif (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {\n> +\t\t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n> +\t\t} else {\n> +\t\t\tif (!prefixcmp(ref, \"refs/\"))\n> +\t\t\t\tref += 5;\n> +\t\t\tif (!prefixcmp(ref, \"notes/\"))\n> +\t\t\t\tref += 6;\n> +\t\t\tstrbuf_addf(sb, \"\\nNotes (%s):\\n\", ref);\n> +\t\t}\n> +\t} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))\n>  \t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n\n\nIt is not clear what the distinction between NOTES_SHOW_HEADER and\nNOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with\nNOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?\n\nI expected to see \"Notes:\\n\" regardless of the mode if the notes is coming\nfrom the default refs/notes/commits tree, but it probably is better to say\n\"Notes (commits):\\n\" like your patch does.\n"},{"id":"135365","messageId":"201002230025.57975.trast@student.ethz.ch","threadId":"22749","inReplyTo":"7v1vgc26zd.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v4 02/11] Support showing notes from more than one notes tree","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-02-22T23:25:57Z","receivedAt":"2010-02-22T23:25:57Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Umm, since I'm currently working on another reroll that has pretty\nargs to add notes refs for display...\n\nOn Tuesday 23 February 2010 00:20:06 Junio C Hamano wrote:\n> Thomas Rast <trast@student.ethz.ch> writes:\n> > +\tif (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {\n> > +\t\tconst char *ref = t->ref;\n> > +\t\tif (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {\n> > +\t\t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n> > +\t\t} else {\n> > +\t\t\tif (!prefixcmp(ref, \"refs/\"))\n> > +\t\t\t\tref += 5;\n> > +\t\t\tif (!prefixcmp(ref, \"notes/\"))\n> > +\t\t\t\tref += 6;\n> > +\t\t\tstrbuf_addf(sb, \"\\nNotes (%s):\\n\", ref);\n> > +\t\t}\n> > +\t} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))\n> >  \t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n> \n> It is not clear what the distinction between NOTES_SHOW_HEADER and\n> NOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with\n> NOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?\n\nNo.\n\n> I expected to see \"Notes:\\n\" regardless of the mode if the notes is coming\n> from the default refs/notes/commits tree, but it probably is better to say\n> \"Notes (commits):\\n\" like your patch does.\n\nI special-cased GIT_NOTES_DEFAULT_REF (which is \"refs/notes/commits\")\nabove *at your request* to not change the output in the default case.\nSo which way do you want it?\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"135371","messageId":"7vzl30u7hs.fsf@alter.siamese.dyndns.org","threadId":"22749","inReplyTo":"201002230025.57975.trast@student.ethz.ch","subject":"Re: [PATCH v4 02/11] Support showing notes from more than one notes tree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-02-23T00:21:35Z","receivedAt":"2010-02-23T00:21:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> Umm, since I'm currently working on another reroll that has pretty\n> args to add notes refs for display...\n>\n> On Tuesday 23 February 2010 00:20:06 Junio C Hamano wrote:\n>> Thomas Rast <trast@student.ethz.ch> writes:\n>> > +\tif (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {\n>> > +\t\tconst char *ref = t->ref;\n>> > +\t\tif (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {\n>> > +\t\t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n>> > +\t\t} else {\n>> > +\t\t\tif (!prefixcmp(ref, \"refs/\"))\n>> > +\t\t\t\tref += 5;\n>> > +\t\t\tif (!prefixcmp(ref, \"notes/\"))\n>> > +\t\t\t\tref += 6;\n>> > +\t\t\tstrbuf_addf(sb, \"\\nNotes (%s):\\n\", ref);\n>> > +\t\t}\n>> > +\t} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))\n>> >  \t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n>> \n>> It is not clear what the distinction between NOTES_SHOW_HEADER and\n>> NOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with\n>> NOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?\n>\n> No.\n\nMeaning nobody will go through the latter \"Notes:\\n\" codepath?  Then what\nis that else clause for?\n\nPerhaps I am not reading your code right in which case this part needs a\nbit more commenting?\n\n>> I expected to see \"Notes:\\n\" regardless of the mode if the notes is coming\n>> from the default refs/notes/commits tree, but it probably is better to say\n>> \"Notes (commits):\\n\" like your patch does.\n>\n> I special-cased GIT_NOTES_DEFAULT_REF (which is \"refs/notes/commits\")\n> above *at your request* to not change the output in the default case.\n\n> So which way do you want it?\n\nI don't have strong preference anymore with the above code.\n\nIf some caller called the latter \"always show Notes:\" codepath, i.e.\nSHOW_HEADER is given but without HEADER_WITH_REF, then my preference would\nhave been more clear.  A caller that gives HEADER_WITH_REF clearly wants\nto show more than one notes (perhaps the user used displayref mechanism)\nand it would be better to always say which one in order to disambiguate,\nand the special case would not be a good idea.  A caller would not give\nHEADER_WITH_REF when the user has refs/notes/commits hierarchy and nothing\nelse, and the user didn't ask for multiple notes trees shown; in such a\ncase we should show the good-old \"Notes:\".\n\nBut if everybody calls with HEADER_WITH_REF, no matter what the end user\npreference is, then it becomes unclear what the right answer would be.\n"}]}