{"thread":{"id":"22647","subject":"[PATCHv13 01/30] Minor cosmetic fixes to notes.c","startedAt":"2010-02-13T21:28:08Z","lastAt":"2010-02-25T03:07:26Z","messageCount":35,"participants":["Johan Herland","Stephen Boyd"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"134469","messageId":"1266096518-2104-1-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":null,"subject":"[PATCHv13 00/30] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:08Z","receivedAt":"2010-02-13T21:28:08Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Hi,\n\nHere is the 13th iteration of the git-notes series. Changes in this\niteration are as follows:\n\n- Patch #1 has improved its commit message\n\n- Patch #7 (the get_note() API function) improved its header file docs.\n\n- Patch #12 (Builtin-ify git-notes) received code to add some details of the\n  current object (the object whose notes are currently being edited)to the\n  template text displayed in the editor. Similar details were originally\n  present in git-notes.sh, but was lost in the initial builtin-ification.\n\n- Patch #22 (the 'list' subcommand) has learned to fail (instead of segfault)\n  when asked to list notes for an object that has none.\n\n- Patch #23 has been inserted to provide --message/--file aliases to the -m/-F\n  options. This improves consistency with other git commands.\n\n- Patch #24 (was #23) has been redone to provide the \"git notes add\" semantics\n  suggested by Junio in the discussion following the previous iteration.\n\n- Patch #25 introduces the \"git notes append\" subcommand which takes on the\n  semantics of \"git notes add\" from the previous iteration.\n\n- Patch #26 deprecates the -m/-F options for \"git notes edit\", since their\n  semantics now overlap with \"git notes add -f\", and the behaviour is more\n  intuitively captured and better documented with \"git notes add -f\"\n\n- Patch #27 refactors some code in builtin-notes, to allow mixing -m and -F\n  options again. This was originally allowed in git-notes.sh, but was\n  disabled by the builin-ification. The refactoring also helps with clear\n  the way for the next patch...\n\n- Patch #28 introduces the -c/-C options to \"git notes add/append\". This\n  allows re-use of existing notes object when making new note.\n  The option names and semantics were inspired by \"git commit\".\n\n- Patch #29 refactors the argc and exit value handling of builtin-notes, in\n  preparation for the next patch\n\n- Patch #30 introduces the \"git notes copy\" subcommand for copying notes from\n  one object to another. This will come in handy when we want to bring notes\n  across a commit rewrite (such as rebase/cherry-pick/amend).\n\n\nThere are ongoing discussions on how to further develop and integrate the\nnotes feature. I try to summarize ideas below, although I don't plan to\nput any more stuff into _this_ patch series:\n\n- Better integration with rebase/amend/cherry-pick. Optionally bring\n  notes across a commit rewrite. Controlled by command-line options\n  and/or config variables. Junio says:\n    I used to fix minor issues (styles, decl-after-stmt, etc.) using\n    rebase-i long after running \"am\" in bulk, but these days I find\n    myself going back to my \"inbox\" and fix them in MUA; this is\n    only because I know these notes do not propagate across rebases\n    and amends -- adjusting the workflow to the tool's limitation is\n    not very good.\n\n- Using multiple notes refs simultaneously. Junio says:\n    The interface to tell tools to use which notes ref to use should be\n    able to say \"these refs\", not just \"this ref\" i.e. GIT_NOTES_REF=a:b\n    just like PATH=a:b:c...); I am fairly certain that we would want to\n    store different kind of information in separate notes trees and\n    aggregate them, as we gain experience with notes.\n  When showing notes from multiple refs, we should also show from _which_\n  note ref a given note originates.\n  Of course, when _writing_ notes, only one ref should receive the created\n  notes.\n\n- Adding command-line options for specifying notes refs. Junio says:\n    There should be an interface to tell tools to use which notes refs via\n    command line options; \"!alias\" does not TAB-complete, and \"git lgm\"\n    above doesn't, either. \"git log --notes=notes/amlog --notes=notes/other\"\n    would probably be the way to go.\n  Jakub suggests adding a --notes-ref=\"a:b:...\" option, either to the git\n  wrapper, or to all git subcommands that can do stuff with notes.\n  Junio adds that the --notes-refs option should DWIM the given ref name\n  (i.e. \"foo\" -> \"refs/notes/foo\").\n\n- Add a \"git notes grep\" subcommand: Junio says:\n    While reviewing the \"inbox\", I sometimes wonder if I applied a message\n    to somewhere already, but there is no obvious way to grep in the notes\n    tree and get the object name that a note is attached to.  Of course I\n    know I can \"git grep -c johan@herland.net notes/amlog\" and it will give\n    me something like:\n\n      notes/amlog:65807ee697a28cb30b8ad38ebb8b84cebd3f255d:1\n      notes/amlog:c789176020d6a008821e01af8b65f28abc138d4b:1\n\n    but this won't scale and needs scripting to mechanize, once we start\n    rebalancing the notes tree with different fan-outs.  The end user (me\n    in this case) is interested in \"set of objects that match this grep\n    criteria\", not \"the pathnames the notes tree's implementation happens\n    to use to store notes for them in the hierarchy\".\n\n- Add support for \"^{notes}\" (or \"^@{}\") to the rev-parse machinery, so\n  that notes can be returned from e.g. \"git cat-file --batch\". This is\n  somewhat complicated when combined with using multiple notes ref\n  simultaneously (see above), but can be solved by using (Jakub's\n  suggestions:) \"^{notes:<namespace>}\" (or \"^@{<namespace>}\").\n\n- Merging notes trees. Junio suspects that we need a specialized merge\n  strategy for notes trees. One example of such a strategy is to\n  auto-concatenate conflicting notes, instead of forcing the user to\n  edit away conflict markers. Note that such a solution should be fairly\n  easy to implement on top of the current notes API, by loading both\n  notes trees into the same struct notes_tree, while using combine_notes\n  == combine_notes_concatenate().\n\n- Matthieu Moy and Sverre Rabbelier suggests to add notes support to\n  format-patch: Notes stored in e.g. the \"refs/notes/format-patch\" ref\n  are added to the \"comments section\" (i.e. following the '---' separator)\n  of generated patches. There is also some discussion on whether notes\n  should be added as custom headers to the email generated by format-patch.\n\n- Special handling of notes on fetch? Tags are autofollowed; does it make\n  sense to do something similar for notes? Guiseppe notes:\n    Tags have the idiosincracy of living in the same namespace regardless\n    of where they come from, and I believe there a little too high a risk\n    of conflicts to do this with notes too. It might make sense to add a\n    default fetch of +refs/notes/*:refs/notes/remotes/<remote>/*, but the\n    real question is what would pushing do, in case of conflicting notes.\n\n- Note objects that are trees (instead of blobs). Peff originally proposed\n  the idea of storing multiple notes for one object in a single notes ref\n  by having the note object itself be a tree which contained note blobs\n  (with well-defined names). Jon Seymour wanted to expand this idea to\n  allow _arbitrary_ tree structures to be stored as notes. However, this\n  will cause problems when trying to interpret notes in a consistent\n  manner (i.e. how would \"git log\" display a tree of notes attached to a\n  single commit). Problems may also arise when optimizing the notes tree\n  structure itself. Peff instead suggests to add another level of\n  indirection, and store the tree _SHA1_ in the note (instead of the tree\n  object itself), and then teach a few tools (git-fsck, git-metadata?) to\n  dereference the SHA1 when needed.\n\n(- Rewrite fast-import notes code to use new notes API with support for\n   non-notes.)\n\n\nHave fun! :)\n\n...Johan\n\n\nJohan Herland (30):\n  Minor cosmetic fixes to notes.c\n  Notes API: get_commit_notes() -> format_note() + remove the commit restriction\n  Add tests for checking correct handling of $GIT_NOTES_REF and core.notesRef\n  Notes API: init_notes(): Initialize the notes tree from the given notes ref\n  Notes API: add_note(): Add note objects to the internal notes tree structure\n  Notes API: remove_note(): Remove note objects from the notes tree structure\n  Notes API: get_note(): Return the note annotating the given object\n  Notes API: for_each_note(): Traverse the entire notes tree with a callback\n  Notes API: write_notes_tree(): Store the notes tree in the database\n  Notes API: Allow multiple concurrent notes trees with new struct notes_tree\n  Refactor notes concatenation into a flexible interface for combining notes\n  Builtin-ify git-notes\n  t3301: Verify successful annotation of non-commits\n  t3305: Verify that adding many notes with git-notes triggers increased fanout\n  Teach notes code to properly preserve non-notes in the notes tree\n  Teach builtin-notes to remove empty notes\n  builtin-notes: Add \"remove\" subcommand for removing existing notes\n  t3305: Verify that removing notes triggers automatic fanout consolidation\n  Notes API: prune_notes(): Prune notes that belong to non-existing objects\n  builtin-notes: Add \"prune\" subcommand for removing notes for missing objects\n  Documentation: Generalize git-notes docs to 'objects' instead of 'commits'\n  builtin-notes: Add \"list\" subcommand for listing note objects\n  builtin-notes: Add --message/--file aliases for -m/-F options\n  builtin-notes: Add \"add\" subcommand for adding notes to objects\n  builtin-notes: Add \"append\" subcommand for appending to note objects\n  builtin-notes: Deprecate the -m/-F options for \"git notes edit\"\n  builtin-notes: Refactor handling of -F option to allow combining -m and -F\n  builtin-notes: Add -c/-C options for reusing notes\n  builtin-notes: Misc. refactoring of argc and exit value handling\n  builtin-notes: Add \"copy\" subcommand for copying notes between objects\n\n Documentation/git-notes.txt                   |   91 +++-\n Makefile                                      |    2 +-\n builtin-notes.c                               |  459 ++++++++++++++\n builtin.h                                     |    3 +\n git-notes.sh => contrib/examples/git-notes.sh |    0\n git.c                                         |    1 +\n notes.c                                       |  843 +++++++++++++++++++++----\n notes.h                                       |  196 ++++++-\n pretty.c                                      |    9 +-\n t/t3301-notes.sh                              |  510 ++++++++++++++-\n t/t3303-notes-subtrees.sh                     |   28 +-\n t/t3304-notes-mixed.sh                        |   36 +-\n t/t3305-notes-fanout.sh                       |   95 +++\n t/t3306-notes-prune.sh                        |   94 +++\n 14 files changed, 2171 insertions(+), 196 deletions(-)\n create mode 100644 builtin-notes.c\n rename git-notes.sh => contrib/examples/git-notes.sh (100%)\n create mode 100755 t/t3305-notes-fanout.sh\n create mode 100755 t/t3306-notes-prune.sh\n"},{"id":"134465","messageId":"1266096518-2104-2-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 01/30] Minor cosmetic fixes to notes.c","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:09Z","receivedAt":"2010-02-13T21:28:09Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |    9 ++++-----\n 1 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 023adce..47e38a1 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1,7 +1,6 @@\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"notes.h\"\n-#include \"refs.h\"\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"tree-walk.h\"\n@@ -93,7 +92,7 @@ static void **note_tree_search(struct int_node **tree,\n \n \ti = GET_NIBBLE(*n, key_sha1);\n \tp = (*tree)->a[i];\n-\tswitch(GET_PTR_TYPE(p)) {\n+\tswitch (GET_PTR_TYPE(p)) {\n \tcase PTR_TYPE_INTERNAL:\n \t\t*tree = CLR_PTR_TYPE(p);\n \t\t(*n)++;\n@@ -195,7 +194,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \n \tassert(GET_PTR_TYPE(entry) == 0); /* no type bits set */\n \tl = (struct leaf_node *) CLR_PTR_TYPE(*p);\n-\tswitch(GET_PTR_TYPE(*p)) {\n+\tswitch (GET_PTR_TYPE(*p)) {\n \tcase PTR_TYPE_NULL:\n \t\tassert(!*p);\n \t\t*p = SET_PTR_TYPE(entry, type);\n@@ -257,7 +256,7 @@ static void note_tree_free(struct int_node *tree)\n \tunsigned int i;\n \tfor (i = 0; i < 16; i++) {\n \t\tvoid *p = tree->a[i];\n-\t\tswitch(GET_PTR_TYPE(p)) {\n+\t\tswitch (GET_PTR_TYPE(p)) {\n \t\tcase PTR_TYPE_INTERNAL:\n \t\t\tnote_tree_free(CLR_PTR_TYPE(p));\n \t\t\t/* fall through */\n@@ -274,7 +273,7 @@ static void note_tree_free(struct int_node *tree)\n  * - hex_len  - Length of above segment. Must be multiple of 2 between 0 and 40\n  * - sha1     - Partial SHA1 value is written here\n  * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20\n- * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format).\n+ * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format)).\n  * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2).\n  * Pads sha1 with NULs up to sha1_len (not included in returned length).\n  */\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134467","messageId":"1266096518-2104-3-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 02/30] Notes API: get_commit_notes() -> format_note() + remove the commit restriction","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:10Z","receivedAt":"2010-02-13T21:28:10Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"There is really no reason why only commit objects can be annotated. By\nchanging the struct commit parameter to get_commit_notes() into a sha1 we\ngain the ability to annotate any object type. To reflect this in the function\nnaming as well, we rename get_commit_notes() to format_note().\n\nThis patch also fixes comments and variable names throughout notes.c as a\nconsequence of the removal of the unnecessary 'commit' restriction.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c  |   33 ++++++++++++++++-----------------\n notes.h  |   11 ++++++++++-\n pretty.c |    8 ++++----\n 3 files changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 47e38a1..4ee4fec 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1,5 +1,4 @@\n #include \"cache.h\"\n-#include \"commit.h\"\n #include \"notes.h\"\n #include \"utf8.h\"\n #include \"strbuf.h\"\n@@ -24,10 +23,10 @@ struct int_node {\n /*\n  * Leaf nodes come in two variants, note entries and subtree entries,\n  * distinguished by the LSb of the leaf node pointer (see above).\n- * As a note entry, the key is the SHA1 of the referenced commit, and the\n+ * As a note entry, the key is the SHA1 of the referenced object, and the\n  * value is the SHA1 of the note object.\n  * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the\n- * referenced commit, using the last byte of the key to store the length of\n+ * referenced object, using the last byte of the key to store the length of\n  * the prefix. The value is the SHA1 of the tree object containing the notes\n  * subtree.\n  */\n@@ -210,7 +209,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \t\t\t\tif (concatenate_notes(l->val_sha1,\n \t\t\t\t\t\tentry->val_sha1))\n \t\t\t\t\tdie(\"failed to concatenate note %s \"\n-\t\t\t\t\t    \"into note %s for commit %s\",\n+\t\t\t\t\t    \"into note %s for object %s\",\n \t\t\t\t\t    sha1_to_hex(entry->val_sha1),\n \t\t\t\t\t    sha1_to_hex(l->val_sha1),\n \t\t\t\t\t    sha1_to_hex(l->key_sha1));\n@@ -298,7 +297,7 @@ static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,\n static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \t\tunsigned int n)\n {\n-\tunsigned char commit_sha1[20];\n+\tunsigned char object_sha1[20];\n \tunsigned int prefix_len;\n \tvoid *buf;\n \tstruct tree_desc desc;\n@@ -311,23 +310,23 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \n \tprefix_len = subtree->key_sha1[19];\n \tassert(prefix_len * 2 >= n);\n-\tmemcpy(commit_sha1, subtree->key_sha1, prefix_len);\n+\tmemcpy(object_sha1, subtree->key_sha1, prefix_len);\n \twhile (tree_entry(&desc, &entry)) {\n \t\tint len = get_sha1_hex_segment(entry.path, strlen(entry.path),\n-\t\t\t\tcommit_sha1 + prefix_len, 20 - prefix_len);\n+\t\t\t\tobject_sha1 + prefix_len, 20 - prefix_len);\n \t\tif (len < 0)\n \t\t\tcontinue; /* entry.path is not a SHA1 sum. Skip */\n \t\tlen += prefix_len;\n \n \t\t/*\n-\t\t * If commit SHA1 is complete (len == 20), assume note object\n-\t\t * If commit SHA1 is incomplete (len < 20), assume note subtree\n+\t\t * If object SHA1 is complete (len == 20), assume note object\n+\t\t * If object SHA1 is incomplete (len < 20), assume note subtree\n \t\t */\n \t\tif (len <= 20) {\n \t\t\tunsigned char type = PTR_TYPE_NOTE;\n \t\t\tstruct leaf_node *l = (struct leaf_node *)\n \t\t\t\txcalloc(sizeof(struct leaf_node), 1);\n-\t\t\thashcpy(l->key_sha1, commit_sha1);\n+\t\t\thashcpy(l->key_sha1, object_sha1);\n \t\t\thashcpy(l->val_sha1, entry.sha1);\n \t\t\tif (len < 20) {\n \t\t\t\tif (!S_ISDIR(entry.mode))\n@@ -343,12 +342,12 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \n static void initialize_notes(const char *notes_ref_name)\n {\n-\tunsigned char sha1[20], commit_sha1[20];\n+\tunsigned char sha1[20], object_sha1[20];\n \tunsigned mode;\n \tstruct leaf_node root_tree;\n \n-\tif (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||\n-\t    get_tree_entry(commit_sha1, \"\", sha1, &mode))\n+\tif (!notes_ref_name || read_ref(notes_ref_name, object_sha1) ||\n+\t    get_tree_entry(object_sha1, \"\", sha1, &mode))\n \t\treturn;\n \n \thashclr(root_tree.key_sha1);\n@@ -356,9 +355,9 @@ static void initialize_notes(const char *notes_ref_name)\n \tload_subtree(&root_tree, &root_node, 0);\n }\n \n-static unsigned char *lookup_notes(const unsigned char *commit_sha1)\n+static unsigned char *lookup_notes(const unsigned char *object_sha1)\n {\n-\tstruct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1);\n+\tstruct leaf_node *found = note_tree_find(&root_node, 0, object_sha1);\n \tif (found)\n \t\treturn found->val_sha1;\n \treturn NULL;\n@@ -371,7 +370,7 @@ void free_notes(void)\n \tinitialized = 0;\n }\n \n-void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n+void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \t\tconst char *output_encoding, int flags)\n {\n \tstatic const char utf8[] = \"utf-8\";\n@@ -390,7 +389,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n \t\tinitialized = 1;\n \t}\n \n-\tsha1 = lookup_notes(commit->object.sha1);\n+\tsha1 = lookup_notes(object_sha1);\n \tif (!sha1)\n \t\treturn;\n \ndiff --git a/notes.h b/notes.h\nindex a1421e3..d745ed1 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -4,10 +4,19 @@\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n+/* Flags controlling how notes are formatted */\n #define NOTES_SHOW_HEADER 1\n #define NOTES_INDENT 2\n \n-void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n+/*\n+ * Fill the given strbuf with the notes associated with the given object.\n+ *\n+ * If the internal notes structure is not initialized, it will be auto-\n+ * initialized to the default value (see documentation for init_notes() above).\n+ *\n+ * 'flags' is a bitwise combination of the above formatting flags.\n+ */\n+void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \t\tconst char *output_encoding, int flags);\n \n #endif\ndiff --git a/pretty.c b/pretty.c\nindex d493cad..076b918 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -775,8 +775,8 @@ 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\tget_commit_notes(commit, sb, git_log_output_encoding ?\n-\t\t\t     git_log_output_encoding : git_commit_encoding, 0);\n+\t\tformat_note(commit->object.sha1, sb, git_log_output_encoding ?\n+\t\t\t    git_log_output_encoding : git_commit_encoding, 0);\n \t\treturn 1;\n \t}\n \n@@ -1095,8 +1095,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\tget_commit_notes(commit, sb, encoding,\n-\t\t\t\t NOTES_SHOW_HEADER | NOTES_INDENT);\n+\t\tformat_note(commit->object.sha1, sb, encoding,\n+\t\t\t    NOTES_SHOW_HEADER | NOTES_INDENT);\n \n \tfree(reencoded);\n }\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134466","messageId":"1266096518-2104-4-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 03/30] Add tests for checking correct handling of $GIT_NOTES_REF and core.notesRef","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:11Z","receivedAt":"2010-02-13T21:28:11Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n t/t3301-notes.sh |   48 ++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 48 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 5d9604b..18aad53 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -206,4 +206,52 @@ do\n \t'\n done\n \n+test_expect_success 'create other note on a different notes ref (setup)' '\n+\t: > a5 &&\n+\tgit add a5 &&\n+\ttest_tick &&\n+\tgit commit -m 5th &&\n+\tGIT_NOTES_REF=\"refs/notes/other\" git notes edit -m \"other note\"\n+'\n+\n+cat > expect-other << EOF\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+    other note\n+EOF\n+\n+cat > expect-not-other << EOF\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 'Do not show note on other ref by default' '\n+\tgit log -1 > output &&\n+\ttest_cmp expect-not-other output\n+'\n+\n+test_expect_success 'Do show note when ref is given in GIT_NOTES_REF' '\n+\tGIT_NOTES_REF=\"refs/notes/other\" git log -1 > output &&\n+\ttest_cmp expect-other output\n+'\n+\n+test_expect_success 'Do show note when ref is given in core.notesRef config' '\n+\tgit config core.notesRef \"refs/notes/other\" &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect-other output\n+'\n+\n+test_expect_success 'Do not show note when core.notesRef is overridden' '\n+\tGIT_NOTES_REF=\"refs/notes/wrong\" git log -1 > output &&\n+\ttest_cmp expect-not-other output\n+'\n+\n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134468","messageId":"1266096518-2104-5-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 04/30] Notes API: init_notes(): Initialize the notes tree from the given notes ref","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:12Z","receivedAt":"2010-02-13T21:28:12Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Created by a simple refactoring of initialize_notes().\n\nAlso add a new 'flags' parameter, which is a bitwise combination of notes\ninitialization flags. For now, there is only one flag - NOTES_INIT_EMPTY -\nwhich indicates that the notes tree should not auto-load the contents of\nthe given (or default) notes ref, but rather should leave the notes tree\ninitialized to an empty state. This will become useful in the future when\nmanipulating the notes tree through the notes API.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   30 ++++++++++++++++++------------\n notes.h |   20 ++++++++++++++++++++\n 2 files changed, 38 insertions(+), 12 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 4ee4fec..3f4ae35 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -340,15 +340,28 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \tfree(buf);\n }\n \n-static void initialize_notes(const char *notes_ref_name)\n+void init_notes(const char *notes_ref, int flags)\n {\n \tunsigned char sha1[20], object_sha1[20];\n \tunsigned mode;\n \tstruct leaf_node root_tree;\n \n-\tif (!notes_ref_name || read_ref(notes_ref_name, object_sha1) ||\n-\t    get_tree_entry(object_sha1, \"\", sha1, &mode))\n+\tassert(!initialized);\n+\tinitialized = 1;\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+\n+\tif (flags & NOTES_INIT_EMPTY || !notes_ref ||\n+\t    read_ref(notes_ref, object_sha1))\n \t\treturn;\n+\tif (get_tree_entry(object_sha1, \"\", sha1, &mode))\n+\t\tdie(\"Failed to read notes tree referenced by %s (%s)\",\n+\t\t    notes_ref, object_sha1);\n \n \thashclr(root_tree.key_sha1);\n \thashcpy(root_tree.val_sha1, sha1);\n@@ -379,15 +392,8 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \tunsigned long linelen, msglen;\n \tenum object_type type;\n \n-\tif (!initialized) {\n-\t\tconst char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);\n-\t\tif (env)\n-\t\t\tnotes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);\n-\t\telse if (!notes_ref_name)\n-\t\t\tnotes_ref_name = GIT_NOTES_DEFAULT_REF;\n-\t\tinitialize_notes(notes_ref_name);\n-\t\tinitialized = 1;\n-\t}\n+\tif (!initialized)\n+\t\tinit_notes(NULL, 0);\n \n \tsha1 = lookup_notes(object_sha1);\n \tif (!sha1)\ndiff --git a/notes.h b/notes.h\nindex d745ed1..6b52799 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -1,6 +1,26 @@\n #ifndef NOTES_H\n #define NOTES_H\n \n+/*\n+ * Flags controlling behaviour of notes tree initialization\n+ *\n+ * Default behaviour is to initialize the notes tree from the tree object\n+ * specified by the given (or default) notes ref.\n+ */\n+#define NOTES_INIT_EMPTY 1\n+\n+/*\n+ * Initialize internal notes tree structure with the notes tree at the given\n+ * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment\n+ * variable is used, and if that is missing, the default notes ref is used\n+ * (\"refs/notes/commits\").\n+ *\n+ * If you need to re-intialize the internal notes tree structure (e.g. loading\n+ * from a different notes ref), please first de-initialize the current notes\n+ * tree by calling free_notes().\n+ */\n+void init_notes(const char *notes_ref, int flags);\n+\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134486","messageId":"1266096518-2104-6-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 05/30] Notes API: add_note(): Add note objects to the internal notes tree structure","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:13Z","receivedAt":"2010-02-13T21:28:13Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   11 +++++++++++\n notes.h |    4 ++++\n 2 files changed, 15 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 3f4ae35..2c0d14e 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -368,6 +368,17 @@ void init_notes(const char *notes_ref, int flags)\n \tload_subtree(&root_tree, &root_node, 0);\n }\n \n+void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1)\n+{\n+\tstruct leaf_node *l;\n+\n+\tassert(initialized);\n+\tl = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));\n+\thashcpy(l->key_sha1, object_sha1);\n+\thashcpy(l->val_sha1, note_sha1);\n+\tnote_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE);\n+}\n+\n static unsigned char *lookup_notes(const unsigned char *object_sha1)\n {\n \tstruct leaf_node *found = note_tree_find(&root_node, 0, object_sha1);\ndiff --git a/notes.h b/notes.h\nindex 6b52799..5f22852 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -21,6 +21,10 @@\n  */\n void init_notes(const char *notes_ref, int flags);\n \n+/* Add the given note object to the internal notes tree structure */\n+void add_note(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1);\n+\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134470","messageId":"1266096518-2104-7-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 06/30] Notes API: remove_note(): Remove note objects from the notes tree structure","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:14Z","receivedAt":"2010-02-13T21:28:14Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This includes adding internal functions for maintaining a healthy notes tree\nstructure after removing individual notes.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n notes.h |    3 ++\n 2 files changed, 87 insertions(+), 1 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 2c0d14e..2e82d71 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -44,7 +44,7 @@ struct leaf_node {\n #define CLR_PTR_TYPE(ptr)       ((void *) ((uintptr_t) (ptr) & ~3))\n #define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type)))\n \n-#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f)\n+#define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f)\n \n #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \\\n \t(memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))\n@@ -249,6 +249,79 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \tnote_tree_insert(new_node, n + 1, entry, type);\n }\n \n+/*\n+ * How to consolidate an int_node:\n+ * If there are > 1 non-NULL entries, give up and return non-zero.\n+ * Otherwise replace the int_node at the given index in the given parent node\n+ * with the only entry (or a NULL entry if no entries) from the given tree,\n+ * and return 0.\n+ */\n+static int note_tree_consolidate(struct int_node *tree,\n+\tstruct int_node *parent, unsigned char index)\n+{\n+\tunsigned int i;\n+\tvoid *p = NULL;\n+\n+\tassert(tree && parent);\n+\tassert(CLR_PTR_TYPE(parent->a[index]) == tree);\n+\n+\tfor (i = 0; i < 16; i++) {\n+\t\tif (GET_PTR_TYPE(tree->a[i]) != PTR_TYPE_NULL) {\n+\t\t\tif (p) /* more than one entry */\n+\t\t\t\treturn -2;\n+\t\t\tp = tree->a[i];\n+\t\t}\n+\t}\n+\n+\t/* replace tree with p in parent[index] */\n+\tparent->a[index] = p;\n+\tfree(tree);\n+\treturn 0;\n+}\n+\n+/*\n+ * To remove a leaf_node:\n+ * Search to the tree location appropriate for the given leaf_node's key:\n+ * - If location does not hold a matching entry, abort and do nothing.\n+ * - Replace the matching leaf_node with a NULL entry (and free the leaf_node).\n+ * - Consolidate int_nodes repeatedly, while walking up the tree towards root.\n+ */\n+static void note_tree_remove(struct int_node *tree, unsigned char n,\n+\t\tstruct leaf_node *entry)\n+{\n+\tstruct leaf_node *l;\n+\tstruct int_node *parent_stack[20];\n+\tunsigned char i, j;\n+\tvoid **p = note_tree_search(&tree, &n, entry->key_sha1);\n+\n+\tassert(GET_PTR_TYPE(entry) == 0); /* no type bits set */\n+\tif (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE)\n+\t\treturn; /* type mismatch, nothing to remove */\n+\tl = (struct leaf_node *) CLR_PTR_TYPE(*p);\n+\tif (hashcmp(l->key_sha1, entry->key_sha1))\n+\t\treturn; /* key mismatch, nothing to remove */\n+\n+\t/* we have found a matching entry */\n+\tfree(l);\n+\t*p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL);\n+\n+\t/* consolidate this tree level, and parent levels, if possible */\n+\tif (!n)\n+\t\treturn; /* cannot consolidate top level */\n+\t/* first, build stack of ancestors between root and current node */\n+\tparent_stack[0] = &root_node;\n+\tfor (i = 0; i < n; i++) {\n+\t\tj = GET_NIBBLE(i, entry->key_sha1);\n+\t\tparent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]);\n+\t}\n+\tassert(i == n && parent_stack[i] == tree);\n+\t/* next, unwind stack until note_tree_consolidate() is done */\n+\twhile (i > 0 &&\n+\t       !note_tree_consolidate(parent_stack[i], parent_stack[i - 1],\n+\t\t\t\t      GET_NIBBLE(i - 1, entry->key_sha1)))\n+\t\ti--;\n+}\n+\n /* Free the entire notes data contained in the given tree */\n static void note_tree_free(struct int_node *tree)\n {\n@@ -379,6 +452,16 @@ void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1)\n \tnote_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE);\n }\n \n+void remove_note(const unsigned char *object_sha1)\n+{\n+\tstruct leaf_node l;\n+\n+\tassert(initialized);\n+\thashcpy(l.key_sha1, object_sha1);\n+\thashclr(l.val_sha1);\n+\treturn note_tree_remove(&root_node, 0, &l);\n+}\n+\n static unsigned char *lookup_notes(const unsigned char *object_sha1)\n {\n \tstruct leaf_node *found = note_tree_find(&root_node, 0, object_sha1);\ndiff --git a/notes.h b/notes.h\nindex 5f22852..9e66855 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -25,6 +25,9 @@ void init_notes(const char *notes_ref, int flags);\n void add_note(const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1);\n \n+/* Remove the given note object from the internal notes tree structure */\n+void remove_note(const unsigned char *object_sha1);\n+\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134471","messageId":"1266096518-2104-8-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 07/30] Notes API: get_note(): Return the note annotating the given object","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:15Z","receivedAt":"2010-02-13T21:28:15Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Created by a simple cleanup and rename of lookup_notes().\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   15 ++++++++-------\n notes.h |    7 +++++++\n 2 files changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 2e82d71..a0a85b4 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -462,12 +462,13 @@ void remove_note(const unsigned char *object_sha1)\n \treturn note_tree_remove(&root_node, 0, &l);\n }\n \n-static unsigned char *lookup_notes(const unsigned char *object_sha1)\n+const unsigned char *get_note(const unsigned char *object_sha1)\n {\n-\tstruct leaf_node *found = note_tree_find(&root_node, 0, object_sha1);\n-\tif (found)\n-\t\treturn found->val_sha1;\n-\treturn NULL;\n+\tstruct leaf_node *found;\n+\n+\tassert(initialized);\n+\tfound = note_tree_find(&root_node, 0, object_sha1);\n+\treturn found ? found->val_sha1 : NULL;\n }\n \n void free_notes(void)\n@@ -481,7 +482,7 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \t\tconst char *output_encoding, int flags)\n {\n \tstatic const char utf8[] = \"utf-8\";\n-\tunsigned char *sha1;\n+\tconst unsigned char *sha1;\n \tchar *msg, *msg_p;\n \tunsigned long linelen, msglen;\n \tenum object_type type;\n@@ -489,7 +490,7 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \tif (!initialized)\n \t\tinit_notes(NULL, 0);\n \n-\tsha1 = lookup_notes(object_sha1);\n+\tsha1 = get_note(object_sha1);\n \tif (!sha1)\n \t\treturn;\n \ndiff --git a/notes.h b/notes.h\nindex 9e66855..0041aec 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -28,6 +28,13 @@ void add_note(const unsigned char *object_sha1,\n /* Remove the given note object from the internal notes tree structure */\n void remove_note(const unsigned char *object_sha1);\n \n+/*\n+ * Get the note object SHA1 containing the note data for the given object\n+ *\n+ * Return NULL if the given object has no notes.\n+ */\n+const unsigned char *get_note(const unsigned char *object_sha1);\n+\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134472","messageId":"1266096518-2104-9-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 08/30] Notes API: for_each_note(): Traverse the entire notes tree with a callback","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:16Z","receivedAt":"2010-02-13T21:28:16Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This includes a first attempt at creating an optimal fanout scheme (which\nis calculated on-the-fly, while traversing).\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n notes.h |   47 ++++++++++++++++++++++\n 2 files changed, 180 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex a0a85b4..eabd6f3 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -413,6 +413,133 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \tfree(buf);\n }\n \n+/*\n+ * Determine optimal on-disk fanout for this part of the notes tree\n+ *\n+ * Given a (sub)tree and the level in the internal tree structure, determine\n+ * whether or not the given existing fanout should be expanded for this\n+ * (sub)tree.\n+ *\n+ * Values of the 'fanout' variable:\n+ * - 0: No fanout (all notes are stored directly in the root notes tree)\n+ * - 1: 2/38 fanout\n+ * - 2: 2/2/36 fanout\n+ * - 3: 2/2/2/34 fanout\n+ * etc.\n+ */\n+static unsigned char determine_fanout(struct int_node *tree, unsigned char n,\n+\t\tunsigned char fanout)\n+{\n+\t/*\n+\t * The following is a simple heuristic that works well in practice:\n+\t * For each even-numbered 16-tree level (remember that each on-disk\n+\t * fanout level corresponds to _two_ 16-tree levels), peek at all 16\n+\t * entries at that tree level. If all of them are either int_nodes or\n+\t * subtree entries, then there are likely plenty of notes below this\n+\t * level, so we return an incremented fanout.\n+\t */\n+\tunsigned int i;\n+\tif ((n % 2) || (n > 2 * fanout))\n+\t\treturn fanout;\n+\tfor (i = 0; i < 16; i++) {\n+\t\tswitch (GET_PTR_TYPE(tree->a[i])) {\n+\t\tcase PTR_TYPE_SUBTREE:\n+\t\tcase PTR_TYPE_INTERNAL:\n+\t\t\tcontinue;\n+\t\tdefault:\n+\t\t\treturn fanout;\n+\t\t}\n+\t}\n+\treturn fanout + 1;\n+}\n+\n+static void construct_path_with_fanout(const unsigned char *sha1,\n+\t\tunsigned char fanout, char *path)\n+{\n+\tunsigned int i = 0, j = 0;\n+\tconst char *hex_sha1 = sha1_to_hex(sha1);\n+\tassert(fanout < 20);\n+\twhile (fanout) {\n+\t\tpath[i++] = hex_sha1[j++];\n+\t\tpath[i++] = hex_sha1[j++];\n+\t\tpath[i++] = '/';\n+\t\tfanout--;\n+\t}\n+\tstrcpy(path + i, hex_sha1 + j);\n+}\n+\n+static int for_each_note_helper(struct int_node *tree, unsigned char n,\n+\t\tunsigned char fanout, int flags, each_note_fn fn,\n+\t\tvoid *cb_data)\n+{\n+\tunsigned int i;\n+\tvoid *p;\n+\tint ret = 0;\n+\tstruct leaf_node *l;\n+\tstatic char path[40 + 19 + 1];  /* hex SHA1 + 19 * '/' + NUL */\n+\n+\tfanout = determine_fanout(tree, n, fanout);\n+\tfor (i = 0; i < 16; i++) {\n+redo:\n+\t\tp = tree->a[i];\n+\t\tswitch (GET_PTR_TYPE(p)) {\n+\t\tcase PTR_TYPE_INTERNAL:\n+\t\t\t/* recurse into int_node */\n+\t\t\tret = for_each_note_helper(CLR_PTR_TYPE(p), n + 1,\n+\t\t\t\tfanout, flags, fn, cb_data);\n+\t\t\tbreak;\n+\t\tcase PTR_TYPE_SUBTREE:\n+\t\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\t\t/*\n+\t\t\t * Subtree entries in the note tree represent parts of\n+\t\t\t * the note tree that have not yet been explored. There\n+\t\t\t * is a direct relationship between subtree entries at\n+\t\t\t * level 'n' in the tree, and the 'fanout' variable:\n+\t\t\t * Subtree entries at level 'n <= 2 * fanout' should be\n+\t\t\t * preserved, since they correspond exactly to a fanout\n+\t\t\t * directory in the on-disk structure. However, subtree\n+\t\t\t * entries at level 'n > 2 * fanout' should NOT be\n+\t\t\t * preserved, but rather consolidated into the above\n+\t\t\t * notes tree level. We achieve this by unconditionally\n+\t\t\t * unpacking subtree entries that exist below the\n+\t\t\t * threshold level at 'n = 2 * fanout'.\n+\t\t\t */\n+\t\t\tif (n <= 2 * fanout &&\n+\t\t\t    flags & FOR_EACH_NOTE_YIELD_SUBTREES) {\n+\t\t\t\t/* invoke callback with subtree */\n+\t\t\t\tunsigned int path_len =\n+\t\t\t\t\tl->key_sha1[19] * 2 + fanout;\n+\t\t\t\tassert(path_len < 40 + 19);\n+\t\t\t\tconstruct_path_with_fanout(l->key_sha1, fanout,\n+\t\t\t\t\t\t\t   path);\n+\t\t\t\t/* Create trailing slash, if needed */\n+\t\t\t\tif (path[path_len - 1] != '/')\n+\t\t\t\t\tpath[path_len++] = '/';\n+\t\t\t\tpath[path_len] = '\\0';\n+\t\t\t\tret = fn(l->key_sha1, l->val_sha1, path,\n+\t\t\t\t\t cb_data);\n+\t\t\t}\n+\t\t\tif (n > fanout * 2 ||\n+\t\t\t    !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) {\n+\t\t\t\t/* unpack subtree and resume traversal */\n+\t\t\t\ttree->a[i] = NULL;\n+\t\t\t\tload_subtree(l, tree, n);\n+\t\t\t\tfree(l);\n+\t\t\t\tgoto redo;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase PTR_TYPE_NOTE:\n+\t\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\t\tconstruct_path_with_fanout(l->key_sha1, fanout, path);\n+\t\t\tret = fn(l->key_sha1, l->val_sha1, path, cb_data);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\treturn 0;\n+}\n+\n void init_notes(const char *notes_ref, int flags)\n {\n \tunsigned char sha1[20], object_sha1[20];\n@@ -471,6 +598,12 @@ const unsigned char *get_note(const unsigned char *object_sha1)\n \treturn found ? found->val_sha1 : NULL;\n }\n \n+int for_each_note(int flags, each_note_fn fn, void *cb_data)\n+{\n+\tassert(initialized);\n+\treturn for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data);\n+}\n+\n void free_notes(void)\n {\n \tnote_tree_free(&root_node);\ndiff --git a/notes.h b/notes.h\nindex 0041aec..2131912 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -35,6 +35,53 @@ void remove_note(const unsigned char *object_sha1);\n  */\n const unsigned char *get_note(const unsigned char *object_sha1);\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\n+ * in the notes tree, unpacking subtree entries along the way.\n+ * The following flags can be used to alter the default behaviour:\n+ *\n+ * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into\n+ *   subtree entries while traversing the notes tree. This causes notes within\n+ *   those subtrees NOT to be passed to the callback. Use this flag if you\n+ *   don't want to traverse _all_ notes, but only want to traverse the parts\n+ *   of the notes tree that have already been unpacked (this includes at least\n+ *   all notes that have been added/changed).\n+ *\n+ * - YIELD_SUBTREES causes any subtree entries that are encountered to be\n+ *   passed to the callback, before recursing into them. Subtree entries are\n+ *   not note objects, but represent intermediate directories in the notes\n+ *   tree. When passed to the callback, subtree entries will have a trailing\n+ *   slash in their path, which the callback may use to differentiate between\n+ *   note entries and subtree entries. Note that already-unpacked subtree\n+ *   entries are not part of the notes tree, and will therefore not be yielded.\n+ *   If this flag is used together with DONT_UNPACK_SUBTREES, for_each_note()\n+ *   will yield the subtree entry, but not recurse into it.\n+ */\n+#define FOR_EACH_NOTE_DONT_UNPACK_SUBTREES 1\n+#define FOR_EACH_NOTE_YIELD_SUBTREES 2\n+\n+/*\n+ * Invoke the specified callback function for each note\n+ *\n+ * If the callback returns nonzero, the note walk is aborted, and the return\n+ * value from the callback is returned from for_each_note(). Hence, a zero\n+ * return value from for_each_note() indicates that all notes were walked\n+ * successfully.\n+ *\n+ * IMPORTANT: The callback function is NOT allowed to change the notes tree.\n+ * In other words, the following functions can NOT be invoked (on the current\n+ * notes tree) from within the callback:\n+ * - add_note()\n+ * - remove_note()\n+ * - free_notes()\n+ */\n+typedef int each_note_fn(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1, char *note_path,\n+\t\tvoid *cb_data);\n+int for_each_note(int flags, each_note_fn fn, void *cb_data);\n+\n /* Free (and de-initialize) the internal notes tree structure */\n void free_notes(void);\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134473","messageId":"1266096518-2104-10-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 09/30] Notes API: write_notes_tree(): Store the notes tree in the database","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:17Z","receivedAt":"2010-02-13T21:28:17Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Uses for_each_note() to traverse the notes tree, and produces tree\nobjects on the fly representing the \"on-disk\" version of the notes\ntree with appropriate fanout.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n notes.h |   38 +++++++++++++++-\n 2 files changed, 180 insertions(+), 3 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex eabd6f3..b576f7e 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"notes.h\"\n+#include \"tree.h\"\n #include \"utf8.h\"\n #include \"strbuf.h\"\n #include \"tree-walk.h\"\n@@ -540,6 +541,126 @@ redo:\n \treturn 0;\n }\n \n+struct tree_write_stack {\n+\tstruct tree_write_stack *next;\n+\tstruct strbuf buf;\n+\tchar path[2]; /* path to subtree in next, if any */\n+};\n+\n+static inline int matches_tree_write_stack(struct tree_write_stack *tws,\n+\t\tconst char *full_path)\n+{\n+\treturn  full_path[0] == tws->path[0] &&\n+\t\tfull_path[1] == tws->path[1] &&\n+\t\tfull_path[2] == '/';\n+}\n+\n+static void write_tree_entry(struct strbuf *buf, unsigned int mode,\n+\t\tconst char *path, unsigned int path_len, const\n+\t\tunsigned char *sha1)\n+{\n+\t\tstrbuf_addf(buf, \"%06o %.*s%c\", mode, path_len, path, '\\0');\n+\t\tstrbuf_add(buf, sha1, 20);\n+}\n+\n+static void tree_write_stack_init_subtree(struct tree_write_stack *tws,\n+\t\tconst char *path)\n+{\n+\tstruct tree_write_stack *n;\n+\tassert(!tws->next);\n+\tassert(tws->path[0] == '\\0' && tws->path[1] == '\\0');\n+\tn = (struct tree_write_stack *)\n+\t\txmalloc(sizeof(struct tree_write_stack));\n+\tn->next = NULL;\n+\tstrbuf_init(&n->buf, 256 * (32 + 40)); /* assume 256 entries per tree */\n+\tn->path[0] = n->path[1] = '\\0';\n+\ttws->next = n;\n+\ttws->path[0] = path[0];\n+\ttws->path[1] = path[1];\n+}\n+\n+static int tree_write_stack_finish_subtree(struct tree_write_stack *tws)\n+{\n+\tint ret;\n+\tstruct tree_write_stack *n = tws->next;\n+\tunsigned char s[20];\n+\tif (n) {\n+\t\tret = tree_write_stack_finish_subtree(n);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tstrbuf_release(&n->buf);\n+\t\tfree(n);\n+\t\ttws->next = NULL;\n+\t\twrite_tree_entry(&tws->buf, 040000, tws->path, 2, s);\n+\t\ttws->path[0] = tws->path[1] = '\\0';\n+\t}\n+\treturn 0;\n+}\n+\n+static int write_each_note_helper(struct tree_write_stack *tws,\n+\t\tconst char *path, unsigned int mode,\n+\t\tconst unsigned char *sha1)\n+{\n+\tsize_t path_len = strlen(path);\n+\tunsigned int n = 0;\n+\tint ret;\n+\n+\t/* Determine common part of tree write stack */\n+\twhile (tws && 3 * n < path_len &&\n+\t       matches_tree_write_stack(tws, path + 3 * n)) {\n+\t\tn++;\n+\t\ttws = tws->next;\n+\t}\n+\n+\t/* tws point to last matching tree_write_stack entry */\n+\tret = tree_write_stack_finish_subtree(tws);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Start subtrees needed to satisfy path */\n+\twhile (3 * n + 2 < path_len && path[3 * n + 2] == '/') {\n+\t\ttree_write_stack_init_subtree(tws, path + 3 * n);\n+\t\tn++;\n+\t\ttws = tws->next;\n+\t}\n+\n+\t/* There should be no more directory components in the given path */\n+\tassert(memchr(path + 3 * n, '/', path_len - (3 * n)) == NULL);\n+\n+\t/* Finally add given entry to the current tree object */\n+\twrite_tree_entry(&tws->buf, mode, path + 3 * n, path_len - (3 * n),\n+\t\t\t sha1);\n+\n+\treturn 0;\n+}\n+\n+struct write_each_note_data {\n+\tstruct tree_write_stack *root;\n+};\n+\n+static int write_each_note(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1, char *note_path,\n+\t\tvoid *cb_data)\n+{\n+\tstruct write_each_note_data *d =\n+\t\t(struct write_each_note_data *) cb_data;\n+\tsize_t note_path_len = strlen(note_path);\n+\tunsigned int mode = 0100644;\n+\n+\tif (note_path[note_path_len - 1] == '/') {\n+\t\t/* subtree entry */\n+\t\tnote_path_len--;\n+\t\tnote_path[note_path_len] = '\\0';\n+\t\tmode = 040000;\n+\t}\n+\tassert(note_path_len <= 40 + 19);\n+\n+\treturn write_each_note_helper(d->root, note_path, mode, note_sha1);\n+}\n+\n void init_notes(const char *notes_ref, int flags)\n {\n \tunsigned char sha1[20], object_sha1[20];\n@@ -604,6 +725,30 @@ int for_each_note(int flags, each_note_fn fn, void *cb_data)\n \treturn for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data);\n }\n \n+int write_notes_tree(unsigned char *result)\n+{\n+\tstruct tree_write_stack root;\n+\tstruct write_each_note_data cb_data;\n+\tint ret;\n+\n+\tassert(initialized);\n+\n+\t/* Prepare for traversal of current notes tree */\n+\troot.next = NULL; /* last forward entry in list is grounded */\n+\tstrbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */\n+\troot.path[0] = root.path[1] = '\\0';\n+\tcb_data.root = &root;\n+\n+\t/* Write tree objects representing current notes tree */\n+\tret = for_each_note(FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |\n+\t\t\t\tFOR_EACH_NOTE_YIELD_SUBTREES,\n+\t\t\twrite_each_note, &cb_data) ||\n+\t\ttree_write_stack_finish_subtree(&root) ||\n+\t\twrite_sha1_file(root.buf.buf, root.buf.len, tree_type, result);\n+\tstrbuf_release(&root.buf);\n+\treturn ret;\n+}\n+\n void free_notes(void)\n {\n \tnote_tree_free(&root_node);\ndiff --git a/notes.h b/notes.h\nindex 2131912..c49b7a5 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -21,11 +21,23 @@\n  */\n void init_notes(const char *notes_ref, int flags);\n \n-/* Add the given note object to the internal notes tree structure */\n+/*\n+ * Add the given note object to the internal notes tree structure\n+ *\n+ * IMPORTANT: The changes made by add_note() to the internal notes tree structure\n+ * are not persistent until a subsequent call to write_notes_tree() returns\n+ * zero.\n+ */\n void add_note(const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1);\n \n-/* Remove the given note object from the internal notes tree structure */\n+/*\n+ * Remove the given note object from the internal notes tree structure\n+ *\n+ * IMPORTANT: The changes made by remove_note() to the internal notes tree\n+ * structure are not persistent until a subsequent call to write_notes_tree()\n+ * returns zero.\n+ */\n void remove_note(const unsigned char *object_sha1);\n \n /*\n@@ -82,7 +94,27 @@ typedef int each_note_fn(const unsigned char *object_sha1,\n \t\tvoid *cb_data);\n int for_each_note(int flags, each_note_fn fn, void *cb_data);\n \n-/* Free (and de-initialize) the internal notes tree structure */\n+/*\n+ * Write the internal notes tree structure to the object database\n+ *\n+ * Creates a new tree object encapsulating the current state of the\n+ * internal notes tree, and stores its SHA1 into the 'result' argument.\n+ *\n+ * Returns zero on success, non-zero on failure.\n+ *\n+ * IMPORTANT: Changes made to the internal notes tree structure are not\n+ * persistent until this function has returned zero. Please also remember\n+ * to create a corresponding commit object, and update the appropriate\n+ * notes ref.\n+ */\n+int write_notes_tree(unsigned char *result);\n+\n+/*\n+ * Free (and de-initialize) the internal notes tree structure\n+ *\n+ * IMPORTANT: Changes made to the notes tree since the last, successful\n+ * call to write_notes_tree() will be lost.\n+ */\n void free_notes(void);\n \n /* Flags controlling how notes are formatted */\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134494","messageId":"1266096518-2104-11-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 10/30] Notes API: Allow multiple concurrent notes trees with new struct notes_tree","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:18Z","receivedAt":"2010-02-13T21:28:18Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The new struct notes_tree encapsulates access to a specific notes tree.\nIt is provided to allow callers to make use of several different notes trees\nsimultaneously.\n\nA struct notes_tree * parameter is added to every function in the notes API.\nIn all cases, NULL can be passed, in which case the fallback \"default\" notes\ntree (default_notes_tree) is used.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c  |   90 ++++++++++++++++++++++++++++++++++++++-----------------------\n notes.h  |   81 +++++++++++++++++++++++++++++++++++--------------------\n pretty.c |    7 +++--\n 3 files changed, 112 insertions(+), 66 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex b576f7e..08a369a 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -50,9 +50,7 @@ struct leaf_node {\n #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \\\n \t(memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))\n \n-static struct int_node root_node;\n-\n-static int initialized;\n+struct notes_tree default_notes_tree;\n \n static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \t\tunsigned int n);\n@@ -287,8 +285,8 @@ static int note_tree_consolidate(struct int_node *tree,\n  * - Replace the matching leaf_node with a NULL entry (and free the leaf_node).\n  * - Consolidate int_nodes repeatedly, while walking up the tree towards root.\n  */\n-static void note_tree_remove(struct int_node *tree, unsigned char n,\n-\t\tstruct leaf_node *entry)\n+static void note_tree_remove(struct notes_tree *t, struct int_node *tree,\n+\t\tunsigned char n, struct leaf_node *entry)\n {\n \tstruct leaf_node *l;\n \tstruct int_node *parent_stack[20];\n@@ -310,7 +308,7 @@ static void note_tree_remove(struct int_node *tree, unsigned char n,\n \tif (!n)\n \t\treturn; /* cannot consolidate top level */\n \t/* first, build stack of ancestors between root and current node */\n-\tparent_stack[0] = &root_node;\n+\tparent_stack[0] = t->root;\n \tfor (i = 0; i < n; i++) {\n \t\tj = GET_NIBBLE(i, entry->key_sha1);\n \t\tparent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]);\n@@ -661,14 +659,15 @@ static int write_each_note(const unsigned char *object_sha1,\n \treturn write_each_note_helper(d->root, note_path, mode, note_sha1);\n }\n \n-void init_notes(const char *notes_ref, int flags)\n+void init_notes(struct notes_tree *t, const char *notes_ref, int flags)\n {\n \tunsigned char sha1[20], object_sha1[20];\n \tunsigned mode;\n \tstruct leaf_node root_tree;\n \n-\tassert(!initialized);\n-\tinitialized = 1;\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(!t->initialized);\n \n \tif (!notes_ref)\n \t\tnotes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);\n@@ -677,6 +676,10 @@ void init_notes(const char *notes_ref, int flags)\n \tif (!notes_ref)\n \t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n \n+\tt->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1);\n+\tt->ref = notes_ref ? xstrdup(notes_ref) : NULL;\n+\tt->initialized = 1;\n+\n \tif (flags & NOTES_INIT_EMPTY || !notes_ref ||\n \t    read_ref(notes_ref, object_sha1))\n \t\treturn;\n@@ -686,52 +689,65 @@ void init_notes(const char *notes_ref, int flags)\n \n \thashclr(root_tree.key_sha1);\n \thashcpy(root_tree.val_sha1, sha1);\n-\tload_subtree(&root_tree, &root_node, 0);\n+\tload_subtree(&root_tree, t->root, 0);\n }\n \n-void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1)\n+void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1)\n {\n \tstruct leaf_node *l;\n \n-\tassert(initialized);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n \tl = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));\n \thashcpy(l->key_sha1, object_sha1);\n \thashcpy(l->val_sha1, note_sha1);\n-\tnote_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE);\n+\tnote_tree_insert(t->root, 0, l, PTR_TYPE_NOTE);\n }\n \n-void remove_note(const unsigned char *object_sha1)\n+void remove_note(struct notes_tree *t, const unsigned char *object_sha1)\n {\n \tstruct leaf_node l;\n \n-\tassert(initialized);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n \thashcpy(l.key_sha1, object_sha1);\n \thashclr(l.val_sha1);\n-\treturn note_tree_remove(&root_node, 0, &l);\n+\treturn note_tree_remove(t, t->root, 0, &l);\n }\n \n-const unsigned char *get_note(const unsigned char *object_sha1)\n+const unsigned char *get_note(struct notes_tree *t,\n+\t\tconst unsigned char *object_sha1)\n {\n \tstruct leaf_node *found;\n \n-\tassert(initialized);\n-\tfound = note_tree_find(&root_node, 0, object_sha1);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n+\tfound = note_tree_find(t->root, 0, object_sha1);\n \treturn found ? found->val_sha1 : NULL;\n }\n \n-int for_each_note(int flags, each_note_fn fn, void *cb_data)\n+int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n+\t\tvoid *cb_data)\n {\n-\tassert(initialized);\n-\treturn for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n+\treturn for_each_note_helper(t->root, 0, 0, flags, fn, cb_data);\n }\n \n-int write_notes_tree(unsigned char *result)\n+int write_notes_tree(struct notes_tree *t, unsigned char *result)\n {\n \tstruct tree_write_stack root;\n \tstruct write_each_note_data cb_data;\n \tint ret;\n \n-\tassert(initialized);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n \n \t/* Prepare for traversal of current notes tree */\n \troot.next = NULL; /* last forward entry in list is grounded */\n@@ -740,7 +756,7 @@ int write_notes_tree(unsigned char *result)\n \tcb_data.root = &root;\n \n \t/* Write tree objects representing current notes tree */\n-\tret = for_each_note(FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |\n+\tret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |\n \t\t\t\tFOR_EACH_NOTE_YIELD_SUBTREES,\n \t\t\twrite_each_note, &cb_data) ||\n \t\ttree_write_stack_finish_subtree(&root) ||\n@@ -749,15 +765,19 @@ int write_notes_tree(unsigned char *result)\n \treturn ret;\n }\n \n-void free_notes(void)\n+void free_notes(struct notes_tree *t)\n {\n-\tnote_tree_free(&root_node);\n-\tmemset(&root_node, 0, sizeof(struct int_node));\n-\tinitialized = 0;\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tif (t->root)\n+\t\tnote_tree_free(t->root);\n+\tfree(t->root);\n+\tfree(t->ref);\n+\tmemset(t, 0, sizeof(struct notes_tree));\n }\n \n-void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n-\t\tconst char *output_encoding, int flags)\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 \tstatic const char utf8[] = \"utf-8\";\n \tconst unsigned char *sha1;\n@@ -765,10 +785,12 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n \tunsigned long linelen, msglen;\n \tenum object_type type;\n \n-\tif (!initialized)\n-\t\tinit_notes(NULL, 0);\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tif (!t->initialized)\n+\t\tinit_notes(t, NULL, 0);\n \n-\tsha1 = get_note(object_sha1);\n+\tsha1 = get_note(t, object_sha1);\n \tif (!sha1)\n \t\treturn;\n \ndiff --git a/notes.h b/notes.h\nindex c49b7a5..12acc38 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -2,6 +2,21 @@\n #define NOTES_H\n \n /*\n+ * Notes tree object\n+ *\n+ * Encapsulates the internal notes tree structure associated with a notes ref.\n+ * Whenever a struct notes_tree pointer is required below, you may pass NULL in\n+ * order to use the default/internal notes tree. E.g. you only need to pass a\n+ * non-NULL value if you need to refer to several different notes trees\n+ * simultaneously.\n+ */\n+extern struct notes_tree {\n+\tstruct int_node *root;\n+\tchar *ref;\n+\tint initialized;\n+} default_notes_tree;\n+\n+/*\n  * Flags controlling behaviour of notes tree initialization\n  *\n  * Default behaviour is to initialize the notes tree from the tree object\n@@ -10,48 +25,54 @@\n #define NOTES_INIT_EMPTY 1\n \n /*\n- * Initialize internal notes tree structure with the notes tree at the given\n+ * Initialize the given notes_tree with the notes tree structure at the given\n  * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment\n  * variable is used, and if that is missing, the default notes ref is used\n  * (\"refs/notes/commits\").\n  *\n- * If you need to re-intialize the internal notes tree structure (e.g. loading\n- * from a different notes ref), please first de-initialize the current notes\n- * tree by calling free_notes().\n+ * If you need to re-intialize a notes_tree structure (e.g. when switching from\n+ * one notes ref to another), you must first de-initialize the notes_tree\n+ * structure by calling free_notes(struct notes_tree *).\n+ *\n+ * If you pass t == NULL, the default internal notes_tree will be initialized.\n+ *\n+ * Precondition: The notes_tree structure is zeroed (this can be achieved with\n+ * memset(t, 0, sizeof(struct notes_tree)))\n  */\n-void init_notes(const char *notes_ref, int flags);\n+void init_notes(struct notes_tree *t, const char *notes_ref, int flags);\n \n /*\n- * Add the given note object to the internal notes tree structure\n+ * Add the given note object to the given notes_tree structure\n  *\n- * IMPORTANT: The changes made by add_note() to the internal notes tree structure\n+ * IMPORTANT: The changes made by add_note() to the given notes_tree structure\n  * are not persistent until a subsequent call to write_notes_tree() returns\n  * zero.\n  */\n-void add_note(const unsigned char *object_sha1,\n+void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1);\n \n /*\n- * Remove the given note object from the internal notes tree structure\n+ * Remove the given note object from the given notes_tree structure\n  *\n- * IMPORTANT: The changes made by remove_note() to the internal notes tree\n+ * IMPORTANT: The changes made by remove_note() to the given notes_tree\n  * structure are not persistent until a subsequent call to write_notes_tree()\n  * returns zero.\n  */\n-void remove_note(const unsigned char *object_sha1);\n+void remove_note(struct notes_tree *t, const unsigned char *object_sha1);\n \n /*\n  * Get the note object SHA1 containing the note data for the given object\n  *\n  * Return NULL if the given object has no notes.\n  */\n-const unsigned char *get_note(const unsigned char *object_sha1);\n+const unsigned char *get_note(struct notes_tree *t,\n+\t\tconst unsigned char *object_sha1);\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\n- * in the notes tree, unpacking subtree entries along the way.\n+ * in the given notes tree, unpacking subtree entries along the way.\n  * The following flags can be used to alter the default behaviour:\n  *\n  * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into\n@@ -75,7 +96,7 @@ const unsigned char *get_note(const unsigned char *object_sha1);\n #define FOR_EACH_NOTE_YIELD_SUBTREES 2\n \n /*\n- * Invoke the specified callback function for each note\n+ * Invoke the specified callback function for each note in the given notes_tree\n  *\n  * If the callback returns nonzero, the note walk is aborted, and the return\n  * value from the callback is returned from for_each_note(). Hence, a zero\n@@ -92,30 +113,30 @@ const unsigned char *get_note(const unsigned char *object_sha1);\n typedef int each_note_fn(const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1, char *note_path,\n \t\tvoid *cb_data);\n-int for_each_note(int flags, each_note_fn fn, void *cb_data);\n+int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n+\t\tvoid *cb_data);\n \n /*\n- * Write the internal notes tree structure to the object database\n+ * Write the given notes_tree structure to the object database\n  *\n- * Creates a new tree object encapsulating the current state of the\n- * internal notes tree, and stores its SHA1 into the 'result' argument.\n+ * Creates a new tree object encapsulating the current state of the given\n+ * notes_tree, and stores its SHA1 into the 'result' argument.\n  *\n  * Returns zero on success, non-zero on failure.\n  *\n- * IMPORTANT: Changes made to the internal notes tree structure are not\n- * persistent until this function has returned zero. Please also remember\n- * to create a corresponding commit object, and update the appropriate\n- * notes ref.\n+ * IMPORTANT: Changes made to the given notes_tree are not persistent until\n+ * this function has returned zero. Please also remember to create a\n+ * corresponding commit object, and update the appropriate notes ref.\n  */\n-int write_notes_tree(unsigned char *result);\n+int write_notes_tree(struct notes_tree *t, unsigned char *result);\n \n /*\n- * Free (and de-initialize) the internal notes tree structure\n+ * Free (and de-initialize) the given notes_tree structure\n  *\n- * IMPORTANT: Changes made to the notes tree since the last, successful\n+ * IMPORTANT: Changes made to the given notes_tree since the last, successful\n  * call to write_notes_tree() will be lost.\n  */\n-void free_notes(void);\n+void free_notes(struct notes_tree *t);\n \n /* Flags controlling how notes are formatted */\n #define NOTES_SHOW_HEADER 1\n@@ -124,12 +145,14 @@ void free_notes(void);\n /*\n  * Fill the given strbuf with the notes associated with the given object.\n  *\n- * If the internal notes structure is not initialized, it will be auto-\n+ * If the given notes_tree structure is not initialized, it will be auto-\n  * initialized to the default value (see documentation for init_notes() above).\n+ * If the given notes_tree is NULL, the internal/default notes_tree will be\n+ * used instead.\n  *\n  * 'flags' is a bitwise combination of the above formatting flags.\n  */\n-void format_note(const unsigned char *object_sha1, struct strbuf *sb,\n-\t\tconst char *output_encoding, int flags);\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 #endif\ndiff --git a/pretty.c b/pretty.c\nindex 076b918..f999485 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -775,8 +775,9 @@ 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(commit->object.sha1, sb, git_log_output_encoding ?\n-\t\t\t    git_log_output_encoding : git_commit_encoding, 0);\n+\t\tformat_note(NULL, 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 \t}\n \n@@ -1095,7 +1096,7 @@ 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(commit->object.sha1, sb, encoding,\n+\t\tformat_note(NULL, commit->object.sha1, sb, encoding,\n \t\t\t    NOTES_SHOW_HEADER | NOTES_INDENT);\n \n \tfree(reencoded);\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134493","messageId":"1266096518-2104-12-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 11/30] Refactor notes concatenation into a flexible interface for combining notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:19Z","receivedAt":"2010-02-13T21:28:19Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"When adding a note to an object that already has an existing note, the\ncurrent solution is to concatenate the contents of the two notes. However,\nthe caller may instead wish to _overwrite_ the existing note with the new\nnote, or maybe even _ignore_ the new note, and keep the existing one. There\nmight also be other ways of combining notes that are only known to the\ncaller.\n\nTherefore, instead of unconditionally concatenating notes, we let the caller\nspecify how to combine notes, by passing in a pointer to a function for\ncombining notes. The caller may choose to implement its own function for\nnotes combining, but normally one of the following three conveniently\nsupplied notes combination functions will be sufficient:\n\n- combine_notes_concatenate() combines the two notes by appending the\n  contents of the new note to the contents of the existing note.\n\n- combine_notes_overwrite() replaces the existing note with the new note.\n\n- combine_notes_ignore() keeps the existing note, and ignores the new note.\n\nA combine_notes function can be passed to init_notes() to choose a default\ncombine_notes function for that notes tree. If NULL is given, the notes tree\nfalls back to combine_notes_concatenate() as the ultimate default.\n\nA combine_notes function can also be passed directly to add_note(), to\ncontrol the notes combining behaviour for a note addition in particular.\nIf NULL is passed, the combine_notes function registered for the given\nnotes tree is used.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  138 ++++++++++++++++++++++++++++++++++++--------------------------\n notes.h |   34 +++++++++++++++-\n 2 files changed, 112 insertions(+), 60 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 08a369a..dc4e4f6 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"notes.h\"\n+#include \"blob.h\"\n #include \"tree.h\"\n #include \"utf8.h\"\n #include \"strbuf.h\"\n@@ -127,55 +128,12 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n \treturn NULL;\n }\n \n-/* Create a new blob object by concatenating the two given blob objects */\n-static int concatenate_notes(unsigned char *cur_sha1,\n-\t\tconst unsigned char *new_sha1)\n-{\n-\tchar *cur_msg, *new_msg, *buf;\n-\tunsigned long cur_len, new_len, buf_len;\n-\tenum object_type cur_type, new_type;\n-\tint ret;\n-\n-\t/* read in both note blob objects */\n-\tnew_msg = read_sha1_file(new_sha1, &new_type, &new_len);\n-\tif (!new_msg || !new_len || new_type != OBJ_BLOB) {\n-\t\tfree(new_msg);\n-\t\treturn 0;\n-\t}\n-\tcur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len);\n-\tif (!cur_msg || !cur_len || cur_type != OBJ_BLOB) {\n-\t\tfree(cur_msg);\n-\t\tfree(new_msg);\n-\t\thashcpy(cur_sha1, new_sha1);\n-\t\treturn 0;\n-\t}\n-\n-\t/* we will separate the notes by a newline anyway */\n-\tif (cur_msg[cur_len - 1] == '\\n')\n-\t\tcur_len--;\n-\n-\t/* concatenate cur_msg and new_msg into buf */\n-\tbuf_len = cur_len + 1 + new_len;\n-\tbuf = (char *) xmalloc(buf_len);\n-\tmemcpy(buf, cur_msg, cur_len);\n-\tbuf[cur_len] = '\\n';\n-\tmemcpy(buf + cur_len + 1, new_msg, new_len);\n-\n-\tfree(cur_msg);\n-\tfree(new_msg);\n-\n-\t/* create a new blob object from buf */\n-\tret = write_sha1_file(buf, buf_len, \"blob\", cur_sha1);\n-\tfree(buf);\n-\treturn ret;\n-}\n-\n /*\n  * To insert a leaf_node:\n  * Search to the tree location appropriate for the given leaf_node's key:\n  * - If location is unused (NULL), store the tweaked pointer directly there\n  * - If location holds a note entry that matches the note-to-be-inserted, then\n- *   concatenate the two notes.\n+ *   combine the two notes (by calling the given combine_notes function).\n  * - If location holds a note entry that matches the subtree-to-be-inserted,\n  *   then unpack the subtree-to-be-inserted into the location.\n  * - If location holds a matching subtree entry, unpack the subtree at that\n@@ -184,7 +142,8 @@ static int concatenate_notes(unsigned char *cur_sha1,\n  *   node-to-be-inserted, and store the new int_node into the location.\n  */\n static void note_tree_insert(struct int_node *tree, unsigned char n,\n-\t\tstruct leaf_node *entry, unsigned char type)\n+\t\tstruct leaf_node *entry, unsigned char type,\n+\t\tcombine_notes_fn combine_notes)\n {\n \tstruct int_node *new_node;\n \tstruct leaf_node *l;\n@@ -205,12 +164,11 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \t\t\t\tif (!hashcmp(l->val_sha1, entry->val_sha1))\n \t\t\t\t\treturn;\n \n-\t\t\t\tif (concatenate_notes(l->val_sha1,\n-\t\t\t\t\t\tentry->val_sha1))\n-\t\t\t\t\tdie(\"failed to concatenate note %s \"\n-\t\t\t\t\t    \"into note %s for object %s\",\n-\t\t\t\t\t    sha1_to_hex(entry->val_sha1),\n+\t\t\t\tif (combine_notes(l->val_sha1, entry->val_sha1))\n+\t\t\t\t\tdie(\"failed to combine notes %s and %s\"\n+\t\t\t\t\t    \" for object %s\",\n \t\t\t\t\t    sha1_to_hex(l->val_sha1),\n+\t\t\t\t\t    sha1_to_hex(entry->val_sha1),\n \t\t\t\t\t    sha1_to_hex(l->key_sha1));\n \t\t\t\tfree(entry);\n \t\t\t\treturn;\n@@ -233,7 +191,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \t\t\t*p = NULL;\n \t\t\tload_subtree(l, tree, n);\n \t\t\tfree(l);\n-\t\t\tnote_tree_insert(tree, n, entry, type);\n+\t\t\tnote_tree_insert(tree, n, entry, type, combine_notes);\n \t\t\treturn;\n \t\t}\n \t\tbreak;\n@@ -243,9 +201,9 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \tassert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||\n \t       GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);\n \tnew_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);\n-\tnote_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p));\n+\tnote_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes);\n \t*p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);\n-\tnote_tree_insert(new_node, n + 1, entry, type);\n+\tnote_tree_insert(new_node, n + 1, entry, type, combine_notes);\n }\n \n /*\n@@ -406,7 +364,8 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \t\t\t\tl->key_sha1[19] = (unsigned char) len;\n \t\t\t\ttype = PTR_TYPE_SUBTREE;\n \t\t\t}\n-\t\t\tnote_tree_insert(node, n, l, type);\n+\t\t\tnote_tree_insert(node, n, l, type,\n+\t\t\t\t\t combine_notes_concatenate);\n \t\t}\n \t}\n \tfree(buf);\n@@ -659,7 +618,64 @@ static int write_each_note(const unsigned char *object_sha1,\n \treturn write_each_note_helper(d->root, note_path, mode, note_sha1);\n }\n \n-void init_notes(struct notes_tree *t, const char *notes_ref, int flags)\n+int combine_notes_concatenate(unsigned char *cur_sha1,\n+\t\tconst unsigned char *new_sha1)\n+{\n+\tchar *cur_msg = NULL, *new_msg = NULL, *buf;\n+\tunsigned long cur_len, new_len, buf_len;\n+\tenum object_type cur_type, new_type;\n+\tint ret;\n+\n+\t/* read in both note blob objects */\n+\tif (!is_null_sha1(new_sha1))\n+\t\tnew_msg = read_sha1_file(new_sha1, &new_type, &new_len);\n+\tif (!new_msg || !new_len || new_type != OBJ_BLOB) {\n+\t\tfree(new_msg);\n+\t\treturn 0;\n+\t}\n+\tif (!is_null_sha1(cur_sha1))\n+\t\tcur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len);\n+\tif (!cur_msg || !cur_len || cur_type != OBJ_BLOB) {\n+\t\tfree(cur_msg);\n+\t\tfree(new_msg);\n+\t\thashcpy(cur_sha1, new_sha1);\n+\t\treturn 0;\n+\t}\n+\n+\t/* we will separate the notes by a newline anyway */\n+\tif (cur_msg[cur_len - 1] == '\\n')\n+\t\tcur_len--;\n+\n+\t/* concatenate cur_msg and new_msg into buf */\n+\tbuf_len = cur_len + 1 + new_len;\n+\tbuf = (char *) xmalloc(buf_len);\n+\tmemcpy(buf, cur_msg, cur_len);\n+\tbuf[cur_len] = '\\n';\n+\tmemcpy(buf + cur_len + 1, new_msg, new_len);\n+\tfree(cur_msg);\n+\tfree(new_msg);\n+\n+\t/* create a new blob object from buf */\n+\tret = write_sha1_file(buf, buf_len, blob_type, cur_sha1);\n+\tfree(buf);\n+\treturn ret;\n+}\n+\n+int combine_notes_overwrite(unsigned char *cur_sha1,\n+\t\tconst unsigned char *new_sha1)\n+{\n+\thashcpy(cur_sha1, new_sha1);\n+\treturn 0;\n+}\n+\n+int combine_notes_ignore(unsigned char *cur_sha1,\n+\t\tconst unsigned char *new_sha1)\n+{\n+\treturn 0;\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 \tunsigned char sha1[20], object_sha1[20];\n \tunsigned mode;\n@@ -676,8 +692,12 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags)\n \tif (!notes_ref)\n \t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n \n+\tif (!combine_notes)\n+\t\tcombine_notes = combine_notes_concatenate;\n+\n \tt->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1);\n \tt->ref = notes_ref ? xstrdup(notes_ref) : NULL;\n+\tt->combine_notes = combine_notes;\n \tt->initialized = 1;\n \n \tif (flags & NOTES_INIT_EMPTY || !notes_ref ||\n@@ -693,17 +713,19 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags)\n }\n \n void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n-\t\tconst unsigned char *note_sha1)\n+\t\tconst unsigned char *note_sha1, combine_notes_fn combine_notes)\n {\n \tstruct leaf_node *l;\n \n \tif (!t)\n \t\tt = &default_notes_tree;\n \tassert(t->initialized);\n+\tif (!combine_notes)\n+\t\tcombine_notes = t->combine_notes;\n \tl = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));\n \thashcpy(l->key_sha1, object_sha1);\n \thashcpy(l->val_sha1, note_sha1);\n-\tnote_tree_insert(t->root, 0, l, PTR_TYPE_NOTE);\n+\tnote_tree_insert(t->root, 0, l, PTR_TYPE_NOTE, combine_notes);\n }\n \n void remove_note(struct notes_tree *t, const unsigned char *object_sha1)\n@@ -788,7 +810,7 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n \tif (!t)\n \t\tt = &default_notes_tree;\n \tif (!t->initialized)\n-\t\tinit_notes(t, NULL, 0);\n+\t\tinit_notes(t, NULL, NULL, 0);\n \n \tsha1 = get_note(t, object_sha1);\n \tif (!sha1)\ndiff --git a/notes.h b/notes.h\nindex 12acc38..20d6e17 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -2,6 +2,30 @@\n #define NOTES_H\n \n /*\n+ * Function type for combining two notes annotating the same object.\n+ *\n+ * When adding a new note annotating the same object as an existing note, it is\n+ * up to the caller to decide how to combine the two notes. The decision is\n+ * made by passing in a function of the following form. The function accepts\n+ * two SHA1s -- of the existing note and the new note, respectively. The\n+ * function then combines the notes in whatever way it sees fit, and writes the\n+ * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return\n+ * value indicates failure.\n+ *\n+ * The two given SHA1s must both be non-NULL and different from each other.\n+ *\n+ * The default combine_notes function (you get this when passing NULL) is\n+ * combine_notes_concatenate(), which appends the contents of the new note to\n+ * the contents of the existing note.\n+ */\n+typedef int combine_notes_fn(unsigned char *cur_sha1, const unsigned char *new_sha1);\n+\n+/* Common notes combinators */\n+int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1);\n+int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1);\n+int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1);\n+\n+/*\n  * Notes tree object\n  *\n  * Encapsulates the internal notes tree structure associated with a notes ref.\n@@ -13,6 +37,7 @@\n extern struct notes_tree {\n \tstruct int_node *root;\n \tchar *ref;\n+\tcombine_notes_fn *combine_notes;\n \tint initialized;\n } default_notes_tree;\n \n@@ -36,10 +61,15 @@ extern struct notes_tree {\n  *\n  * If you pass t == NULL, the default internal notes_tree will be initialized.\n  *\n+ * The combine_notes function that is passed becomes the default combine_notes\n+ * function for the given notes_tree. If NULL is passed, the default\n+ * combine_notes function is combine_notes_concatenate().\n+ *\n  * Precondition: The notes_tree structure is zeroed (this can be achieved with\n  * memset(t, 0, sizeof(struct notes_tree)))\n  */\n-void init_notes(struct notes_tree *t, const char *notes_ref, int flags);\n+void init_notes(struct notes_tree *t, const char *notes_ref,\n+\t\tcombine_notes_fn combine_notes, int flags);\n \n /*\n  * Add the given note object to the given notes_tree structure\n@@ -49,7 +79,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags);\n  * zero.\n  */\n void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n-\t\tconst unsigned char *note_sha1);\n+\t\tconst unsigned char *note_sha1, combine_notes_fn combine_notes);\n \n /*\n  * Remove the given note object from the given notes_tree structure\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134480","messageId":"1266096518-2104-13-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 12/30] Builtin-ify git-notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:20Z","receivedAt":"2010-02-13T21:28:20Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The builtin-ification includes some minor behavioural changes to the\ncommand-line interface: It is no longer allowed to mix the -m and -F\narguments, and it is not allowed to use multiple -F options.\n\nAs part of the builtin-ification, we add the commit_notes() function\nto the builtin API. This function (together with the notes.h API) can\nbe easily used from other builtins to manipulate the notes tree.\n\nAlso includes needed changes to t3301.\n\nThis patch has been improved by the following contributions:\n- Stephen Boyd: Use die() instead of fprintf(stderr, ...) followed by exit(1)\n\nCc: Stephen Boyd <bebarino@gmail.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt                   |    6 +-\n Makefile                                      |    2 +-\n builtin-notes.c                               |  280 +++++++++++++++++++++++++\n builtin.h                                     |    3 +\n git-notes.sh => contrib/examples/git-notes.sh |    0\n git.c                                         |    1 +\n t/t3301-notes.sh                              |   98 ++++++---\n 7 files changed, 350 insertions(+), 40 deletions(-)\n create mode 100644 builtin-notes.c\n rename git-notes.sh => contrib/examples/git-notes.sh (100%)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex d4487ca..0d1ada6 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -37,14 +37,12 @@ OPTIONS\n -------\n -m <msg>::\n \tUse the given note message (instead of prompting).\n-\tIf multiple `-m` (or `-F`) options are given, their\n-\tvalues are concatenated as separate paragraphs.\n+\tIf multiple `-m` options are given, their values\n+\tare concatenated as separate paragraphs.\n \n -F <file>::\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n-\tIf multiple `-F` (or `-m`) options are given, their\n-\tvalues are concatenated as separate paragraphs.\n \n \n Author\ndiff --git a/Makefile b/Makefile\nindex c0dbee2..1f95f93 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -353,7 +353,6 @@ SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-mergetool--lib.sh\n-SCRIPT_SH += git-notes.sh\n SCRIPT_SH += git-parse-remote.sh\n SCRIPT_SH += git-pull.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -671,6 +670,7 @@ BUILTIN_OBJS += builtin-mktag.o\n BUILTIN_OBJS += builtin-mktree.o\n BUILTIN_OBJS += builtin-mv.o\n BUILTIN_OBJS += builtin-name-rev.o\n+BUILTIN_OBJS += builtin-notes.o\n BUILTIN_OBJS += builtin-pack-objects.o\n BUILTIN_OBJS += builtin-pack-redundant.o\n BUILTIN_OBJS += builtin-pack-refs.o\ndiff --git a/builtin-notes.c b/builtin-notes.c\nnew file mode 100644\nindex 0000000..89aa6e0\n--- /dev/null\n+++ b/builtin-notes.c\n@@ -0,0 +1,280 @@\n+/*\n+ * Builtin \"git notes\"\n+ *\n+ * Copyright (c) 2010 Johan Herland <johan@herland.net>\n+ *\n+ * Based on git-notes.sh by Johannes Schindelin,\n+ * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"notes.h\"\n+#include \"blob.h\"\n+#include \"commit.h\"\n+#include \"refs.h\"\n+#include \"exec_cmd.h\"\n+#include \"run-command.h\"\n+#include \"parse-options.h\"\n+\n+static const char * const git_notes_usage[] = {\n+\t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n+\t\"git notes show [<object>]\",\n+\tNULL\n+};\n+\n+static const char note_template[] =\n+\t\"\\n\"\n+\t\"#\\n\"\n+\t\"# Write/edit the notes for the following object:\\n\"\n+\t\"#\\n\";\n+\n+static void write_note_data(int fd, const unsigned char *sha1)\n+{\n+\tunsigned long size;\n+\tenum object_type type;\n+\tchar *buf = read_sha1_file(sha1, &type, &size);\n+\tif (buf) {\n+\t\tif (size)\n+\t\t\twrite_or_die(fd, buf, size);\n+\t\tfree(buf);\n+\t}\n+}\n+\n+static void write_commented_object(int fd, const unsigned char *object)\n+{\n+\tconst char *show_args[5] =\n+\t\t{\"show\", \"--stat\", \"--no-notes\", sha1_to_hex(object), NULL};\n+\tstruct child_process show;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tFILE *show_out;\n+\n+\t/* Invoke \"git show --stat --no-notes $object\" */\n+\tmemset(&show, 0, sizeof(show));\n+\tshow.argv = show_args;\n+\tshow.no_stdin = 1;\n+\tshow.out = -1;\n+\tshow.err = 0;\n+\tshow.git_cmd = 1;\n+\tif (start_command(&show))\n+\t\tdie(\"unable to start 'show' for object '%s'\",\n+\t\t    sha1_to_hex(object));\n+\n+\t/* Open the output as FILE* so strbuf_getline() can be used. */\n+\tshow_out = xfdopen(show.out, \"r\");\n+\tif (show_out == NULL)\n+\t\tdie_errno(\"can't fdopen 'show' output fd\");\n+\n+\t/* Prepend \"# \" to each output line and write result to 'fd' */\n+\twhile (strbuf_getline(&buf, show_out, '\\n') != EOF) {\n+\t\twrite_or_die(fd, \"# \", 2);\n+\t\twrite_or_die(fd, buf.buf, buf.len);\n+\t\twrite_or_die(fd, \"\\n\", 1);\n+\t}\n+\tstrbuf_release(&buf);\n+\tif (fclose(show_out))\n+\t\tdie_errno(\"failed to close pipe to 'show' for object '%s'\",\n+\t\t\t  sha1_to_hex(object));\n+\tif (finish_command(&show))\n+\t\tdie(\"failed to finish 'show' for object '%s'\",\n+\t\t    sha1_to_hex(object));\n+}\n+\n+static void create_note(const unsigned char *object,\n+\t\t\tstruct strbuf *buf,\n+\t\t\tint skip_editor,\n+\t\t\tconst unsigned char *prev,\n+\t\t\tunsigned char *result)\n+{\n+\tchar *path = NULL;\n+\n+\tif (!skip_editor) {\n+\t\tint fd;\n+\n+\t\t/* write the template message before editing: */\n+\t\tpath = git_pathdup(\"NOTES_EDITMSG\");\n+\t\tfd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);\n+\t\tif (fd < 0)\n+\t\t\tdie_errno(\"could not create file '%s'\", path);\n+\n+\t\tif (prev)\n+\t\t\twrite_note_data(fd, prev);\n+\t\twrite_or_die(fd, note_template, strlen(note_template));\n+\n+\t\twrite_commented_object(fd, object);\n+\n+\t\tclose(fd);\n+\n+\t\tif (launch_editor(path, buf, NULL)) {\n+\t\t\tdie(\"Please supply the note contents using either -m\" \\\n+\t\t\t    \" or -F option\");\n+\t\t}\n+\t}\n+\n+\tstripspace(buf, 1);\n+\n+\tif (!skip_editor && !buf->len) {\n+\t\tfprintf(stderr, \"Removing note for object %s\\n\",\n+\t\t\tsha1_to_hex(object));\n+\t\thashclr(result);\n+\t} else {\n+\t\tif (write_sha1_file(buf->buf, buf->len, blob_type, result)) {\n+\t\t\terror(\"unable to write note object\");\n+\t\t\tif (path)\n+\t\t\t\terror(\"The note contents has been left in %s\",\n+\t\t\t\t      path);\n+\t\t\texit(128);\n+\t\t}\n+\t}\n+\n+\tif (path) {\n+\t\tunlink_or_warn(path);\n+\t\tfree(path);\n+\t}\n+}\n+\n+struct msg_arg {\n+\tint given;\n+\tstruct strbuf buf;\n+};\n+\n+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct msg_arg *msg = opt->value;\n+\n+\tif (!arg)\n+\t\treturn -1;\n+\tif (msg->buf.len)\n+\t\tstrbuf_addstr(&(msg->buf), \"\\n\\n\");\n+\tstrbuf_addstr(&(msg->buf), arg);\n+\tmsg->given = 1;\n+\treturn 0;\n+}\n+\n+int commit_notes(struct notes_tree *t, const char *msg)\n+{\n+\tstruct commit_list *parent;\n+\tunsigned char tree_sha1[20], prev_commit[20], new_commit[20];\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tif (!t->initialized || !t->ref || !*t->ref)\n+\t\tdie(\"Cannot commit uninitialized/unreferenced notes tree\");\n+\n+\t/* Prepare commit message and reflog message */\n+\tstrbuf_addstr(&buf, \"notes: \"); /* commit message starts at index 7 */\n+\tstrbuf_addstr(&buf, msg);\n+\tif (buf.buf[buf.len - 1] != '\\n')\n+\t\tstrbuf_addch(&buf, '\\n'); /* Make sure msg ends with newline */\n+\n+\t/* Convert notes tree to tree object */\n+\tif (write_notes_tree(t, tree_sha1))\n+\t\tdie(\"Failed to write current notes tree to database\");\n+\n+\t/* Create new commit for the tree object */\n+\tif (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */\n+\t\tparent = xmalloc(sizeof(*parent));\n+\t\tparent->item = lookup_commit(prev_commit);\n+\t\tparent->next = NULL;\n+\t} else {\n+\t\thashclr(prev_commit);\n+\t\tparent = NULL;\n+\t}\n+\tif (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))\n+\t\tdie(\"Failed to commit notes tree to database\");\n+\n+\t/* Update notes ref with new commit */\n+\tupdate_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);\n+\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\n+\n+int cmd_notes(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct notes_tree *t;\n+\tunsigned char object[20], new_note[20];\n+\tconst unsigned char *note;\n+\tconst char *object_ref;\n+\tint edit = 0, show = 0;\n+\tconst char *msgfile = NULL;\n+\tstruct msg_arg msg = { 0, STRBUF_INIT };\n+\tstruct option options[] = {\n+\t\tOPT_GROUP(\"Notes edit options\"),\n+\t\tOPT_CALLBACK('m', NULL, &msg, \"msg\",\n+\t\t\t     \"note contents as a string\", parse_msg_arg),\n+\t\tOPT_FILENAME('F', NULL, &msgfile, \"note contents in a file\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tgit_config(git_default_config, NULL);\n+\n+\targc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);\n+\n+\tif (argc && !strcmp(argv[0], \"edit\"))\n+\t\tedit = 1;\n+\telse if (argc && !strcmp(argv[0], \"show\"))\n+\t\tshow = 1;\n+\n+\tif (edit + show != 1)\n+\t\tusage_with_options(git_notes_usage, options);\n+\n+\tobject_ref = argc == 2 ? argv[1] : \"HEAD\";\n+\tif (argc > 2) {\n+\t\terror(\"too many parameters\");\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n+\tif (get_sha1(object_ref, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\n+\tinit_notes(NULL, NULL, NULL, 0);\n+\tt = &default_notes_tree;\n+\n+\tif (prefixcmp(t->ref, \"refs/notes/\"))\n+\t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n+\t\t    edit ? \"edit\" : \"show\", t->ref);\n+\n+\tnote = get_note(t, object);\n+\n+\t/* show command */\n+\n+\tif (show && !note) {\n+\t\terror(\"No note found for object %s.\", sha1_to_hex(object));\n+\t\treturn 1;\n+\t} else if (show) {\n+\t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n+\t\treturn execv_git_cmd(show_args);\n+\t}\n+\n+\t/* edit command */\n+\n+\tif (msg.given || msgfile) {\n+\t\tif (msg.given && msgfile) {\n+\t\t\terror(\"mixing -m and -F options is not allowed.\");\n+\t\t\tusage_with_options(git_notes_usage, options);\n+\t\t}\n+\t\tif (msg.given)\n+\t\t\tstrbuf_addbuf(&buf, &(msg.buf));\n+\t\telse {\n+\t\t\tif (!strcmp(msgfile, \"-\")) {\n+\t\t\t\tif (strbuf_read(&buf, 0, 1024) < 0)\n+\t\t\t\t\tdie_errno(\"cannot read '%s'\", msgfile);\n+\t\t\t} else {\n+\t\t\t\tif (strbuf_read_file(&buf, msgfile, 1024) < 0)\n+\t\t\t\t\tdie_errno(\"could not open or read '%s'\",\n+\t\t\t\t\t\tmsgfile);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tcreate_note(object, &buf, msg.given || msgfile, note, new_note);\n+\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\tcommit_notes(t, \"Note added by 'git notes edit'\");\n+\n+\tfree_notes(t);\n+\tstrbuf_release(&buf);\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex e8202f3..cdf9847 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n+#include \"notes.h\"\n \n extern const char git_version_string[];\n extern const char git_usage_string[];\n@@ -18,6 +19,7 @@ extern int fmt_merge_msg(int merge_summary, struct strbuf *in,\n 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 extern int check_pager_config(const char *cmd);\n \n extern int cmd_add(int argc, const char **argv, const char *prefix);\n@@ -78,6 +80,7 @@ extern int cmd_mktag(int argc, const char **argv, const char *prefix);\n extern int cmd_mktree(int argc, const char **argv, const char *prefix);\n extern int cmd_mv(int argc, const char **argv, const char *prefix);\n extern int cmd_name_rev(int argc, const char **argv, const char *prefix);\n+extern int cmd_notes(int argc, const char **argv, const char *prefix);\n extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);\n extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix);\n extern int cmd_patch_id(int argc, const char **argv, const char *prefix);\ndiff --git a/git-notes.sh b/contrib/examples/git-notes.sh\nsimilarity index 100%\nrename from git-notes.sh\nrename to contrib/examples/git-notes.sh\ndiff --git a/git.c b/git.c\nindex b3e23f1..32f76e1 100644\n--- a/git.c\n+++ b/git.c\n@@ -343,6 +343,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t\t{ \"mv\", cmd_mv, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"name-rev\", cmd_name_rev, RUN_SETUP },\n+\t\t{ \"notes\", cmd_notes, RUN_SETUP },\n \t\t{ \"pack-objects\", cmd_pack_objects, RUN_SETUP },\n \t\t{ \"pack-redundant\", cmd_pack_redundant, RUN_SETUP },\n \t\t{ \"patch-id\", cmd_patch_id },\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 18aad53..10f62f4 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -12,8 +12,8 @@ echo \"$MSG\" > \"$1\"\n echo \"$MSG\" >& 2\n EOF\n chmod a+x fake_editor.sh\n-VISUAL=./fake_editor.sh\n-export VISUAL\n+GIT_EDITOR=./fake_editor.sh\n+export GIT_EDITOR\n \n test_expect_success 'cannot annotate non-existing HEAD' '\n \t(MSG=3 && export MSG && test_must_fail git notes edit)\n@@ -56,8 +56,17 @@ test_expect_success 'handle empty notes gracefully' '\n \n test_expect_success 'create notes' '\n \tgit config core.notesRef refs/notes/commits &&\n+\tMSG=b0 git notes edit &&\n+\ttest ! -f .git/NOTES_EDITMSG &&\n+\ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n+\ttest b0 = $(git notes show) &&\n+\tgit show HEAD^ &&\n+\ttest_must_fail git notes show HEAD^\n+'\n+\n+test_expect_success 'edit existing notes' '\n \tMSG=b1 git notes edit &&\n-\ttest ! -f .git/new-notes &&\n+\ttest ! -f .git/NOTES_EDITMSG &&\n \ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n \ttest b1 = $(git notes show) &&\n \tgit show HEAD^ &&\n@@ -110,19 +119,16 @@ test_expect_success 'show multi-line notes' '\n \tgit log -2 > output &&\n \ttest_cmp expect-multiline output\n '\n-test_expect_success 'create -m and -F notes (setup)' '\n+test_expect_success 'create -F notes (setup)' '\n \t: > a4 &&\n \tgit add a4 &&\n \ttest_tick &&\n \tgit commit -m 4th &&\n \techo \"xyzzy\" > note5 &&\n-\tgit notes edit -m spam -F note5 -m \"foo\n-bar\n-baz\"\n+\tgit notes edit -F note5\n '\n \n-whitespace=\"    \"\n-cat > expect-m-and-F << EOF\n+cat > expect-F << EOF\n commit 15023535574ded8b1a89052b32673f84cf9582b8\n Author: A U Thor <author@example.com>\n Date:   Thu Apr 7 15:16:13 2005 -0700\n@@ -130,21 +136,15 @@ Date:   Thu Apr 7 15:16:13 2005 -0700\n     4th\n \n Notes:\n-    spam\n-$whitespace\n     xyzzy\n-$whitespace\n-    foo\n-    bar\n-    baz\n EOF\n \n-printf \"\\n\" >> expect-m-and-F\n-cat expect-multiline >> expect-m-and-F\n+printf \"\\n\" >> expect-F\n+cat expect-multiline >> expect-F\n \n-test_expect_success 'show -m and -F notes' '\n+test_expect_success 'show -F notes' '\n \tgit log -3 > output &&\n-\ttest_cmp expect-m-and-F output\n+\ttest_cmp expect-F output\n '\n \n cat >expect << EOF\n@@ -164,13 +164,7 @@ test_expect_success 'git log --pretty=raw does not show notes' '\n cat >>expect <<EOF\n \n Notes:\n-    spam\n-$whitespace\n     xyzzy\n-$whitespace\n-    foo\n-    bar\n-    baz\n EOF\n test_expect_success 'git log --show-notes' '\n \tgit log -1 --pretty=raw --show-notes >output &&\n@@ -179,17 +173,17 @@ test_expect_success 'git log --show-notes' '\n \n test_expect_success 'git log --no-notes' '\n \tgit log -1 --no-notes >output &&\n-\t! grep spam output\n+\t! grep xyzzy output\n '\n \n test_expect_success 'git format-patch does not show notes' '\n \tgit format-patch -1 --stdout >output &&\n-\t! grep spam output\n+\t! grep xyzzy output\n '\n \n test_expect_success 'git format-patch --show-notes does show notes' '\n \tgit format-patch --show-notes -1 --stdout >output &&\n-\tgrep spam output\n+\tgrep xyzzy output\n '\n \n for pretty in \\\n@@ -202,19 +196,22 @@ do\n \tesac\n \ttest_expect_success \"git show $pretty does$not show notes\" '\n \t\tgit show $p >output &&\n-\t\teval \"$negate grep spam output\"\n+\t\teval \"$negate grep xyzzy output\"\n \t'\n done\n \n-test_expect_success 'create other note on a different notes ref (setup)' '\n+test_expect_success 'create -m notes (setup)' '\n \t: > a5 &&\n \tgit add a5 &&\n \ttest_tick &&\n \tgit commit -m 5th &&\n-\tGIT_NOTES_REF=\"refs/notes/other\" git notes edit -m \"other note\"\n+\tgit notes edit -m spam -m \"foo\n+bar\n+baz\"\n '\n \n-cat > expect-other << EOF\n+whitespace=\"    \"\n+cat > expect-m << EOF\n commit bd1753200303d0a0344be813e504253b3d98e74d\n Author: A U Thor <author@example.com>\n Date:   Thu Apr 7 15:17:13 2005 -0700\n@@ -222,15 +219,46 @@ Date:   Thu Apr 7 15:17:13 2005 -0700\n     5th\n \n Notes:\n+    spam\n+$whitespace\n+    foo\n+    bar\n+    baz\n+EOF\n+\n+printf \"\\n\" >> expect-m\n+cat expect-F >> expect-m\n+\n+test_expect_success 'show -m notes' '\n+\tgit log -4 > output &&\n+\ttest_cmp expect-m output\n+'\n+\n+test_expect_success 'create other note on a different notes ref (setup)' '\n+\t: > a6 &&\n+\tgit add a6 &&\n+\ttest_tick &&\n+\tgit commit -m 6th &&\n+\tGIT_NOTES_REF=\"refs/notes/other\" git notes edit -m \"other note\"\n+'\n+\n+cat > expect-other << 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     other note\n EOF\n \n cat > expect-not-other << EOF\n-commit bd1753200303d0a0344be813e504253b3d98e74d\n+commit 387a89921c73d7ed72cd94d179c1c7048ca47756\n Author: A U Thor <author@example.com>\n-Date:   Thu Apr 7 15:17:13 2005 -0700\n+Date:   Thu Apr 7 15:18:13 2005 -0700\n \n-    5th\n+    6th\n EOF\n \n test_expect_success 'Do not show note on other ref by default' '\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134474","messageId":"1266096518-2104-14-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 13/30] t3301: Verify successful annotation of non-commits","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:21Z","receivedAt":"2010-02-13T21:28:21Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Adds a testcase verifying that git-notes works successfully on\ntree, blob, and tag objects.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n t/t3301-notes.sh |   17 +++++++++++++++++\n 1 files changed, 17 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 10f62f4..fd5e593 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -282,4 +282,21 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '\n \ttest_cmp expect-not-other output\n '\n \n+test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '\n+\techo \"Note on a tree\" > expect\n+\tgit notes edit -m \"Note on a tree\" HEAD: &&\n+\tgit notes show HEAD: > actual &&\n+\ttest_cmp expect actual &&\n+\techo \"Note on a blob\" > expect\n+\tfilename=$(git ls-tree --name-only HEAD | head -n1) &&\n+\tgit notes edit -m \"Note on a blob\" HEAD:$filename &&\n+\tgit notes show HEAD:$filename > actual &&\n+\ttest_cmp expect actual &&\n+\techo \"Note on a tag\" > expect\n+\tgit tag -a -m \"This is an annotated tag\" foobar HEAD^ &&\n+\tgit notes edit -m \"Note on a tag\" foobar &&\n+\tgit notes show foobar > actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134481","messageId":"1266096518-2104-15-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 14/30] t3305: Verify that adding many notes with git-notes triggers increased fanout","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:22Z","receivedAt":"2010-02-13T21:28:22Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Add a test verifying that the notes code automatically restructures the\nnotes tree into a deeper fanout level, when many notes are added with\n\"git notes\".\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n t/t3305-notes-fanout.sh |   50 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 50 insertions(+), 0 deletions(-)\n create mode 100755 t/t3305-notes-fanout.sh\n\ndiff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh\nnew file mode 100755\nindex 0000000..823b0ff\n--- /dev/null\n+++ b/t/t3305-notes-fanout.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Test that adding many notes triggers automatic fanout restructuring'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'creating many notes with git-notes' '\n+\tnum_notes=300 &&\n+\ti=0 &&\n+\twhile test $i -lt $num_notes\n+\tdo\n+\t\ti=$(($i + 1)) &&\n+\t\ttest_tick &&\n+\t\techo \"file for commit #$i\" > file &&\n+\t\tgit add file &&\n+\t\tgit commit -q -m \"commit #$i\" &&\n+\t\tgit notes edit -m \"note #$i\" || return 1\n+\tdone\n+'\n+\n+test_expect_success 'many notes created correctly with git-notes' '\n+\tgit log | grep \"^    \" > output &&\n+\ti=300 &&\n+\twhile test $i -gt 0\n+\tdo\n+\t\techo \"    commit #$i\" &&\n+\t\techo \"    note #$i\" &&\n+\t\ti=$(($i - 1));\n+\tdone > expect &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'many notes created with git-notes triggers fanout' '\n+\t# Expect entire notes tree to have a fanout == 1\n+\tgit ls-tree -r --name-only refs/notes/commits |\n+\twhile read path\n+\tdo\n+\t\tcase \"$path\" in\n+\t\t??/??????????????????????????????????????)\n+\t\t\t: true\n+\t\t\t;;\n+\t\t*)\n+\t\t\techo \"Invalid path \\\"$path\\\"\" &&\n+\t\t\treturn 1\n+\t\t\t;;\n+\t\tesac\n+\tdone\n+'\n+\n+test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134477","messageId":"1266096518-2104-16-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 15/30] Teach notes code to properly preserve non-notes in the notes tree","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:23Z","receivedAt":"2010-02-13T21:28:23Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The note tree structure allows for non-note entries to coexist with note\nentries in a notes tree. Although we certainly expect there to be very\nfew non-notes in a notes tree, we should still support them to a certain\ndegree.\n\nThis patch teaches the notes code to preserve non-notes when updating the\nnotes tree with write_notes_tree(). Non-notes are not affected by fanout\nrestructuring.\n\nFor non-notes to be handled correctly, we can no longer allow subtree\nentries that do not match the fanout structure produced by the notes code\nitself. This means that fanouts like 4/36, 6/34, 8/32, 4/4/32, etc. are\nno longer recognized as note subtrees; only 2-based fanouts are allowed\n(2/38, 2/2/36, 2/2/2/34, etc.). Since the notes code has never at any point\n_produced_ non-2-based fanouts, it is highly unlikely that this change will\ncause problems for anyone.\n\nThe patch also adds some tests verifying the correct handling of non-notes\nin a notes tree.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c                   |  219 +++++++++++++++++++++++++++++++++++++--------\n notes.h                   |    1 +\n t/t3303-notes-subtrees.sh |   28 ++++---\n t/t3304-notes-mixed.sh    |   36 +++++++-\n 4 files changed, 233 insertions(+), 51 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex dc4e4f6..d432517 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -37,6 +37,21 @@ struct leaf_node {\n \tunsigned char val_sha1[20];\n };\n \n+/*\n+ * A notes tree may contain entries that are not notes, and that do not follow\n+ * the naming conventions of notes. There are typically none/few of these, but\n+ * we still need to keep track of them. Keep a simple linked list sorted alpha-\n+ * betically on the non-note path. The list is populated when parsing tree\n+ * objects in load_subtree(), and the non-notes are correctly written back into\n+ * the tree objects produced by write_notes_tree().\n+ */\n+struct non_note {\n+\tstruct non_note *next; /* grounded (last->next == NULL) */\n+\tchar *path;\n+\tunsigned int mode;\n+\tunsigned char sha1[20];\n+};\n+\n #define PTR_TYPE_NULL     0\n #define PTR_TYPE_INTERNAL 1\n #define PTR_TYPE_NOTE     2\n@@ -53,8 +68,8 @@ struct leaf_node {\n \n struct notes_tree default_notes_tree;\n \n-static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n-\t\tunsigned int 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 /*\n  * Search the tree until the appropriate location for the given key is found:\n@@ -71,7 +86,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n  *      - an unused leaf node (NULL)\n  *      In any case, set *tree and *n, and return pointer to the tree location.\n  */\n-static void **note_tree_search(struct int_node **tree,\n+static void **note_tree_search(struct notes_tree *t, struct int_node **tree,\n \t\tunsigned char *n, const unsigned char *key_sha1)\n {\n \tstruct leaf_node *l;\n@@ -83,9 +98,9 @@ static void **note_tree_search(struct int_node **tree,\n \t\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n \t\t\t/* unpack tree and resume search */\n \t\t\t(*tree)->a[0] = NULL;\n-\t\t\tload_subtree(l, *tree, *n);\n+\t\t\tload_subtree(t, l, *tree, *n);\n \t\t\tfree(l);\n-\t\t\treturn note_tree_search(tree, n, key_sha1);\n+\t\t\treturn note_tree_search(t, tree, n, key_sha1);\n \t\t}\n \t}\n \n@@ -95,15 +110,15 @@ static void **note_tree_search(struct int_node **tree,\n \tcase PTR_TYPE_INTERNAL:\n \t\t*tree = CLR_PTR_TYPE(p);\n \t\t(*n)++;\n-\t\treturn note_tree_search(tree, n, key_sha1);\n+\t\treturn note_tree_search(t, tree, n, key_sha1);\n \tcase PTR_TYPE_SUBTREE:\n \t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n \t\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n \t\t\t/* unpack tree and resume search */\n \t\t\t(*tree)->a[i] = NULL;\n-\t\t\tload_subtree(l, *tree, *n);\n+\t\t\tload_subtree(t, l, *tree, *n);\n \t\t\tfree(l);\n-\t\t\treturn note_tree_search(tree, n, key_sha1);\n+\t\t\treturn note_tree_search(t, tree, n, key_sha1);\n \t\t}\n \t\t/* fall through */\n \tdefault:\n@@ -116,10 +131,11 @@ static void **note_tree_search(struct int_node **tree,\n  * Search to the tree location appropriate for the given key:\n  * If a note entry with matching key, return the note entry, else return NULL.\n  */\n-static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n+static struct leaf_node *note_tree_find(struct notes_tree *t,\n+\t\tstruct int_node *tree, unsigned char n,\n \t\tconst unsigned char *key_sha1)\n {\n-\tvoid **p = note_tree_search(&tree, &n, key_sha1);\n+\tvoid **p = note_tree_search(t, &tree, &n, key_sha1);\n \tif (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) {\n \t\tstruct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p);\n \t\tif (!hashcmp(key_sha1, l->key_sha1))\n@@ -141,13 +157,13 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n  * - Else, create a new int_node, holding both the node-at-location and the\n  *   node-to-be-inserted, and store the new int_node into the location.\n  */\n-static void note_tree_insert(struct int_node *tree, unsigned char n,\n-\t\tstruct leaf_node *entry, unsigned char type,\n+static void note_tree_insert(struct notes_tree *t, struct int_node *tree,\n+\t\tunsigned char n, struct leaf_node *entry, unsigned char type,\n \t\tcombine_notes_fn combine_notes)\n {\n \tstruct int_node *new_node;\n \tstruct leaf_node *l;\n-\tvoid **p = note_tree_search(&tree, &n, entry->key_sha1);\n+\tvoid **p = note_tree_search(t, &tree, &n, entry->key_sha1);\n \n \tassert(GET_PTR_TYPE(entry) == 0); /* no type bits set */\n \tl = (struct leaf_node *) CLR_PTR_TYPE(*p);\n@@ -178,7 +194,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \t\t\tif (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,\n \t\t\t\t\t\t    entry->key_sha1)) {\n \t\t\t\t/* unpack 'entry' */\n-\t\t\t\tload_subtree(entry, tree, n);\n+\t\t\t\tload_subtree(t, entry, tree, n);\n \t\t\t\tfree(entry);\n \t\t\t\treturn;\n \t\t\t}\n@@ -189,9 +205,10 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \t\tif (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {\n \t\t\t/* unpack 'l' and restart insert */\n \t\t\t*p = NULL;\n-\t\t\tload_subtree(l, tree, n);\n+\t\t\tload_subtree(t, l, tree, n);\n \t\t\tfree(l);\n-\t\t\tnote_tree_insert(tree, n, entry, type, combine_notes);\n+\t\t\tnote_tree_insert(t, tree, n, entry, type,\n+\t\t\t\t\t combine_notes);\n \t\t\treturn;\n \t\t}\n \t\tbreak;\n@@ -201,9 +218,10 @@ static void note_tree_insert(struct int_node *tree, unsigned char n,\n \tassert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE ||\n \t       GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE);\n \tnew_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1);\n-\tnote_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes);\n+\tnote_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p),\n+\t\t\t combine_notes);\n \t*p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);\n-\tnote_tree_insert(new_node, n + 1, entry, type, combine_notes);\n+\tnote_tree_insert(t, new_node, n + 1, entry, type, combine_notes);\n }\n \n /*\n@@ -249,7 +267,7 @@ static void note_tree_remove(struct notes_tree *t, struct int_node *tree,\n \tstruct leaf_node *l;\n \tstruct int_node *parent_stack[20];\n \tunsigned char i, j;\n-\tvoid **p = note_tree_search(&tree, &n, entry->key_sha1);\n+\tvoid **p = note_tree_search(t, &tree, &n, entry->key_sha1);\n \n \tassert(GET_PTR_TYPE(entry) == 0); /* no type bits set */\n \tif (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE)\n@@ -324,14 +342,67 @@ static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,\n \treturn len;\n }\n \n-static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n-\t\tunsigned int n)\n+static int non_note_cmp(const struct non_note *a, const struct non_note *b)\n+{\n+\treturn strcmp(a->path, b->path);\n+}\n+\n+static void add_non_note(struct notes_tree *t, const char *path,\n+\t\tunsigned int mode, const unsigned char *sha1)\n+{\n+\tstruct non_note *p = t->prev_non_note, *n;\n+\tn = (struct non_note *) xmalloc(sizeof(struct non_note));\n+\tn->next = NULL;\n+\tn->path = xstrdup(path);\n+\tn->mode = mode;\n+\thashcpy(n->sha1, sha1);\n+\tt->prev_non_note = n;\n+\n+\tif (!t->first_non_note) {\n+\t\tt->first_non_note = n;\n+\t\treturn;\n+\t}\n+\n+\tif (non_note_cmp(p, n) < 0)\n+\t\t; /* do nothing  */\n+\telse if (non_note_cmp(t->first_non_note, n) <= 0)\n+\t\tp = t->first_non_note;\n+\telse {\n+\t\t/* n sorts before t->first_non_note */\n+\t\tn->next = t->first_non_note;\n+\t\tt->first_non_note = n;\n+\t\treturn;\n+\t}\n+\n+\t/* n sorts equal or after p */\n+\twhile (p->next && non_note_cmp(p->next, n) <= 0)\n+\t\tp = p->next;\n+\n+\tif (non_note_cmp(p, n) == 0) { /* n ~= p; overwrite p with n */\n+\t\tassert(strcmp(p->path, n->path) == 0);\n+\t\tp->mode = n->mode;\n+\t\thashcpy(p->sha1, n->sha1);\n+\t\tfree(n);\n+\t\tt->prev_non_note = p;\n+\t\treturn;\n+\t}\n+\n+\t/* n sorts between p and p->next */\n+\tn->next = p->next;\n+\tp->next = n;\n+}\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 \tunsigned char object_sha1[20];\n \tunsigned int prefix_len;\n \tvoid *buf;\n \tstruct tree_desc desc;\n \tstruct name_entry entry;\n+\tint len, path_len;\n+\tunsigned char type;\n+\tstruct leaf_node *l;\n \n \tbuf = fill_tree_descriptor(&desc, subtree->val_sha1);\n \tif (!buf)\n@@ -342,31 +413,68 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \tassert(prefix_len * 2 >= n);\n \tmemcpy(object_sha1, subtree->key_sha1, prefix_len);\n \twhile (tree_entry(&desc, &entry)) {\n-\t\tint len = get_sha1_hex_segment(entry.path, strlen(entry.path),\n+\t\tpath_len = strlen(entry.path);\n+\t\tlen = get_sha1_hex_segment(entry.path, path_len,\n \t\t\t\tobject_sha1 + prefix_len, 20 - prefix_len);\n \t\tif (len < 0)\n-\t\t\tcontinue; /* entry.path is not a SHA1 sum. Skip */\n+\t\t\tgoto handle_non_note; /* entry.path is not a SHA1 */\n \t\tlen += prefix_len;\n \n \t\t/*\n \t\t * If object SHA1 is complete (len == 20), assume note object\n-\t\t * If object SHA1 is incomplete (len < 20), assume note subtree\n+\t\t * If object SHA1 is incomplete (len < 20), and current\n+\t\t * component consists of 2 hex chars, assume note subtree\n \t\t */\n \t\tif (len <= 20) {\n-\t\t\tunsigned char type = PTR_TYPE_NOTE;\n-\t\t\tstruct leaf_node *l = (struct leaf_node *)\n+\t\t\ttype = PTR_TYPE_NOTE;\n+\t\t\tl = (struct leaf_node *)\n \t\t\t\txcalloc(sizeof(struct leaf_node), 1);\n \t\t\thashcpy(l->key_sha1, object_sha1);\n \t\t\thashcpy(l->val_sha1, entry.sha1);\n \t\t\tif (len < 20) {\n-\t\t\t\tif (!S_ISDIR(entry.mode))\n-\t\t\t\t\tcontinue; /* entry cannot be subtree */\n+\t\t\t\tif (!S_ISDIR(entry.mode) || path_len != 2)\n+\t\t\t\t\tgoto handle_non_note; /* not subtree */\n \t\t\t\tl->key_sha1[19] = (unsigned char) len;\n \t\t\t\ttype = PTR_TYPE_SUBTREE;\n \t\t\t}\n-\t\t\tnote_tree_insert(node, n, l, type,\n+\t\t\tnote_tree_insert(t, node, n, l, type,\n \t\t\t\t\t combine_notes_concatenate);\n \t\t}\n+\t\tcontinue;\n+\n+handle_non_note:\n+\t\t/*\n+\t\t * Determine full path for this non-note entry:\n+\t\t * The filename is already found in entry.path, but the\n+\t\t * directory part of the path must be deduced from the subtree\n+\t\t * containing this entry. We assume here that the overall notes\n+\t\t * tree follows a strict byte-based progressive fanout\n+\t\t * structure (i.e. using 2/38, 2/2/36, etc. fanouts, and not\n+\t\t * e.g. 4/36 fanout). This means that if a non-note is found at\n+\t\t * path \"dead/beef\", the following code will register it as\n+\t\t * being found on \"de/ad/beef\".\n+\t\t * On the other hand, if you use such non-obvious non-note\n+\t\t * paths in the middle of a notes tree, you deserve what's\n+\t\t * coming to you ;). Note that for non-notes that are not\n+\t\t * SHA1-like at the top level, there will be no problems.\n+\t\t *\n+\t\t * To conclude, it is strongly advised to make sure non-notes\n+\t\t * have at least one non-hex character in the top-level path\n+\t\t * component.\n+\t\t */\n+\t\t{\n+\t\t\tchar non_note_path[PATH_MAX];\n+\t\t\tchar *p = non_note_path;\n+\t\t\tconst char *q = sha1_to_hex(subtree->key_sha1);\n+\t\t\tint i;\n+\t\t\tfor (i = 0; i < prefix_len; i++) {\n+\t\t\t\t*p++ = *q++;\n+\t\t\t\t*p++ = *q++;\n+\t\t\t\t*p++ = '/';\n+\t\t\t}\n+\t\t\tstrcpy(p, entry.path);\n+\t\t\tadd_non_note(t, non_note_path, entry.mode, entry.sha1);\n+\t\t}\n \t}\n \tfree(buf);\n }\n@@ -426,9 +534,9 @@ static void construct_path_with_fanout(const unsigned char *sha1,\n \tstrcpy(path + i, hex_sha1 + j);\n }\n \n-static int for_each_note_helper(struct int_node *tree, unsigned char n,\n-\t\tunsigned char fanout, int flags, each_note_fn fn,\n-\t\tvoid *cb_data)\n+static int for_each_note_helper(struct notes_tree *t, struct int_node *tree,\n+\t\tunsigned char n, unsigned char fanout, int flags,\n+\t\teach_note_fn fn, void *cb_data)\n {\n \tunsigned int i;\n \tvoid *p;\n@@ -443,7 +551,7 @@ redo:\n \t\tswitch (GET_PTR_TYPE(p)) {\n \t\tcase PTR_TYPE_INTERNAL:\n \t\t\t/* recurse into int_node */\n-\t\t\tret = for_each_note_helper(CLR_PTR_TYPE(p), n + 1,\n+\t\t\tret = for_each_note_helper(t, CLR_PTR_TYPE(p), n + 1,\n \t\t\t\tfanout, flags, fn, cb_data);\n \t\t\tbreak;\n \t\tcase PTR_TYPE_SUBTREE:\n@@ -481,7 +589,7 @@ redo:\n \t\t\t    !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) {\n \t\t\t\t/* unpack subtree and resume traversal */\n \t\t\t\ttree->a[i] = NULL;\n-\t\t\t\tload_subtree(l, tree, n);\n+\t\t\t\tload_subtree(t, l, tree, n);\n \t\t\t\tfree(l);\n \t\t\t\tgoto redo;\n \t\t\t}\n@@ -596,8 +704,29 @@ static int write_each_note_helper(struct tree_write_stack *tws,\n \n struct write_each_note_data {\n \tstruct tree_write_stack *root;\n+\tstruct non_note *next_non_note;\n };\n \n+static int write_each_non_note_until(const char *note_path,\n+\t\tstruct write_each_note_data *d)\n+{\n+\tstruct non_note *n = d->next_non_note;\n+\tint cmp, ret;\n+\twhile (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) {\n+\t\tif (note_path && cmp == 0)\n+\t\t\t; /* do nothing, prefer note to non-note */\n+\t\telse {\n+\t\t\tret = write_each_note_helper(d->root, n->path, n->mode,\n+\t\t\t\t\t\t     n->sha1);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\t\tn = n->next;\n+\t}\n+\td->next_non_note = n;\n+\treturn 0;\n+}\n+\n static int write_each_note(const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1, char *note_path,\n \t\tvoid *cb_data)\n@@ -615,7 +744,9 @@ static int write_each_note(const unsigned char *object_sha1,\n \t}\n \tassert(note_path_len <= 40 + 19);\n \n-\treturn write_each_note_helper(d->root, note_path, mode, note_sha1);\n+\t/* Weave non-note entries into note entries */\n+\treturn  write_each_non_note_until(note_path, d) ||\n+\t\twrite_each_note_helper(d->root, note_path, mode, note_sha1);\n }\n \n int combine_notes_concatenate(unsigned char *cur_sha1,\n@@ -696,6 +827,8 @@ void init_notes(struct notes_tree *t, const char *notes_ref,\n \t\tcombine_notes = combine_notes_concatenate;\n \n \tt->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1);\n+\tt->first_non_note = NULL;\n+\tt->prev_non_note = NULL;\n \tt->ref = notes_ref ? xstrdup(notes_ref) : NULL;\n \tt->combine_notes = combine_notes;\n \tt->initialized = 1;\n@@ -709,7 +842,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,\n \n \thashclr(root_tree.key_sha1);\n \thashcpy(root_tree.val_sha1, sha1);\n-\tload_subtree(&root_tree, t->root, 0);\n+\tload_subtree(t, &root_tree, t->root, 0);\n }\n \n void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n@@ -725,7 +858,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n \tl = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));\n \thashcpy(l->key_sha1, object_sha1);\n \thashcpy(l->val_sha1, note_sha1);\n-\tnote_tree_insert(t->root, 0, l, PTR_TYPE_NOTE, combine_notes);\n+\tnote_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes);\n }\n \n void remove_note(struct notes_tree *t, const unsigned char *object_sha1)\n@@ -748,7 +881,7 @@ const unsigned char *get_note(struct notes_tree *t,\n \tif (!t)\n \t\tt = &default_notes_tree;\n \tassert(t->initialized);\n-\tfound = note_tree_find(t->root, 0, object_sha1);\n+\tfound = note_tree_find(t, t->root, 0, object_sha1);\n \treturn found ? found->val_sha1 : NULL;\n }\n \n@@ -758,7 +891,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n \tif (!t)\n \t\tt = &default_notes_tree;\n \tassert(t->initialized);\n-\treturn for_each_note_helper(t->root, 0, 0, flags, fn, cb_data);\n+\treturn for_each_note_helper(t, t->root, 0, 0, flags, fn, cb_data);\n }\n \n int write_notes_tree(struct notes_tree *t, unsigned char *result)\n@@ -776,11 +909,13 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result)\n \tstrbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */\n \troot.path[0] = root.path[1] = '\\0';\n \tcb_data.root = &root;\n+\tcb_data.next_non_note = t->first_non_note;\n \n \t/* Write tree objects representing current notes tree */\n \tret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES |\n \t\t\t\tFOR_EACH_NOTE_YIELD_SUBTREES,\n \t\t\twrite_each_note, &cb_data) ||\n+\t\twrite_each_non_note_until(NULL, &cb_data) ||\n \t\ttree_write_stack_finish_subtree(&root) ||\n \t\twrite_sha1_file(root.buf.buf, root.buf.len, tree_type, result);\n \tstrbuf_release(&root.buf);\n@@ -794,6 +929,12 @@ void free_notes(struct notes_tree *t)\n \tif (t->root)\n \t\tnote_tree_free(t->root);\n \tfree(t->root);\n+\twhile (t->first_non_note) {\n+\t\tt->prev_non_note = t->first_non_note->next;\n+\t\tfree(t->first_non_note->path);\n+\t\tfree(t->first_non_note);\n+\t\tt->first_non_note = t->prev_non_note;\n+\t}\n \tfree(t->ref);\n \tmemset(t, 0, sizeof(struct notes_tree));\n }\ndiff --git a/notes.h b/notes.h\nindex 20d6e17..f98578f 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -36,6 +36,7 @@ int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1)\n  */\n extern struct notes_tree {\n \tstruct int_node *root;\n+\tstruct non_note *first_non_note, *prev_non_note;\n \tchar *ref;\n \tcombine_notes_fn *combine_notes;\n \tint initialized;\ndiff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh\nindex edc4bc8..75ec187 100755\n--- a/t/t3303-notes-subtrees.sh\n+++ b/t/t3303-notes-subtrees.sh\n@@ -95,12 +95,12 @@ INPUT_END\n test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based \"s|^..|&/|\"'\n test_expect_success 'verify notes in 2/38-fanout' 'verify_notes'\n \n-test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based \"s|^....|&/|\"'\n-test_expect_success 'verify notes in 4/36-fanout' 'verify_notes'\n-\n test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\"'\n test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'\n \n+test_expect_success 'test notes in 2/2/2/34-fanout' 'test_sha1_based \"s|^\\(..\\)\\(..\\)\\(..\\)|\\1/\\2/\\3/|\"'\n+test_expect_success 'verify notes in 2/2/2/34-fanout' 'verify_notes'\n+\n test_same_notes () {\n \t(\n \t\tstart_note_commit &&\n@@ -128,14 +128,17 @@ INPUT_END\n \tgit fast-import --quiet\n }\n \n-test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes \"s|^..|&/|\" \"s|^....|&/|\"'\n-test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes'\n+test_expect_success 'test same notes in no fanout and 2/38-fanout' 'test_same_notes \"s|^..|&/|\" \"\"'\n+test_expect_success 'verify same notes in no fanout and 2/38-fanout' 'verify_notes'\n+\n+test_expect_success 'test same notes in no fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"\"'\n+test_expect_success 'verify same notes in no fanout and 2/2/36-fanout' 'verify_notes'\n \n test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^..|&/|\"'\n test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes'\n \n-test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^....|&/|\"'\n-test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes'\n+test_expect_success 'test same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^\\(..\\)\\(..\\)\\(..\\)|\\1/\\2/\\3/|\"'\n+test_expect_success 'verify same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'verify_notes'\n \n test_concatenated_notes () {\n \t(\n@@ -176,13 +179,16 @@ verify_concatenated_notes () {\n     test_cmp expect output\n }\n \n-test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes \"s|^..|&/|\" \"s|^....|&/|\"'\n-test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes'\n+test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes \"s|^..|&/|\" \"\"'\n+test_expect_success 'verify notes in no fanout concatenated with 2/38-fanout' 'verify_concatenated_notes'\n+\n+test_expect_success 'test notes in no fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"\"'\n+test_expect_success 'verify notes in no fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'\n \n test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^..|&/|\"'\n test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'\n \n-test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^....|&/|\"'\n-test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'\n+test_expect_success 'test notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)\\(..\\)|\\1/\\2/\\3/|\" \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\"'\n+test_expect_success 'verify notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'verify_concatenated_notes'\n \n test_done\ndiff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh\nindex 256687f..c975a6d 100755\n--- a/t/t3304-notes-mixed.sh\n+++ b/t/t3304-notes-mixed.sh\n@@ -131,6 +131,17 @@ data <<EOF\n another non-note with SHA1-like name\n EOF\n \n+M 644 inline de/adbeefdeadbeefdeadbeefdeadbeefdeadbeef\n+data <<EOF\n+This is actually a valid note, albeit to a non-existing object.\n+It is needed in order to trigger the \"mishandling\" of the dead/beef non-note.\n+EOF\n+\n+M 644 inline dead/beef\n+data <<EOF\n+yet another non-note with SHA1-like name\n+EOF\n+\n INPUT_END\n \tgit fast-import --quiet <input &&\n \tgit config core.notesRef refs/notes/commits\n@@ -158,6 +169,9 @@ EXPECT_END\n cat >expect_nn3 <<EXPECT_END\n another non-note with SHA1-like name\n EXPECT_END\n+cat >expect_nn4 <<EXPECT_END\n+yet another non-note with SHA1-like name\n+EXPECT_END\n \n test_expect_success \"verify contents of non-notes\" '\n \n@@ -166,7 +180,27 @@ test_expect_success \"verify contents of non-notes\" '\n \tgit cat-file -p refs/notes/commits:deadbeef > actual_nn2 &&\n \ttest_cmp expect_nn2 actual_nn2 &&\n \tgit cat-file -p refs/notes/commits:de/adbeef > actual_nn3 &&\n-\ttest_cmp expect_nn3 actual_nn3\n+\ttest_cmp expect_nn3 actual_nn3 &&\n+\tgit cat-file -p refs/notes/commits:dead/beef > actual_nn4 &&\n+\ttest_cmp expect_nn4 actual_nn4\n+'\n+\n+test_expect_success \"git-notes preserves non-notes\" '\n+\n+\ttest_tick &&\n+\tgit notes edit -m \"foo bar\"\n+'\n+\n+test_expect_success \"verify contents of non-notes after git-notes\" '\n+\n+\tgit cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 &&\n+\ttest_cmp expect_nn1 actual_nn1 &&\n+\tgit cat-file -p refs/notes/commits:deadbeef > actual_nn2 &&\n+\ttest_cmp expect_nn2 actual_nn2 &&\n+\tgit cat-file -p refs/notes/commits:de/adbeef > actual_nn3 &&\n+\ttest_cmp expect_nn3 actual_nn3 &&\n+\tgit cat-file -p refs/notes/commits:dead/beef > actual_nn4 &&\n+\ttest_cmp expect_nn4 actual_nn4\n '\n \n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134475","messageId":"1266096518-2104-17-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 16/30] Teach builtin-notes to remove empty notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:24Z","receivedAt":"2010-02-13T21:28:24Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"When the result of editing a note is an empty string, the associated note\nentry should be deleted from the notes tree.\n\nThis allows deleting notes by invoking either \"git notes -m ''\" or\n\"git notes -F /dev/null\".\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n builtin-notes.c  |   15 +++++++++++----\n t/t3301-notes.sh |   31 +++++++++++++++++++++++++++++++\n 2 files changed, 42 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 89aa6e0..7b4cb13 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -113,7 +113,7 @@ static void create_note(const unsigned char *object,\n \n \tstripspace(buf, 1);\n \n-\tif (!skip_editor && !buf->len) {\n+\tif (!buf->len) {\n \t\tfprintf(stderr, \"Removing note for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t\thashclr(result);\n@@ -197,7 +197,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tunsigned char object[20], new_note[20];\n \tconst unsigned char *note;\n-\tconst char *object_ref;\n+\tconst char *object_ref, *logmsg;\n+\n \tint edit = 0, show = 0;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n@@ -271,8 +272,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t}\n \n \tcreate_note(object, &buf, msg.given || msgfile, note, new_note);\n-\tadd_note(t, object, new_note, combine_notes_overwrite);\n-\tcommit_notes(t, \"Note added by 'git notes edit'\");\n+\tif (is_null_sha1(new_note)) {\n+\t\tremove_note(t, object);\n+\t\tlogmsg = \"Note removed by 'git notes edit'\";\n+\t} else {\n+\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\t\tlogmsg = \"Note added by 'git notes edit'\";\n+\t}\n+\tcommit_notes(t, logmsg);\n \n \tfree_notes(t);\n \tstrbuf_release(&buf);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex fd5e593..fe59e73 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -234,6 +234,37 @@ test_expect_success 'show -m notes' '\n \ttest_cmp expect-m output\n '\n \n+test_expect_success 'remove note with -F /dev/null (setup)' '\n+\tgit notes edit -F /dev/null\n+'\n+\n+cat > expect-rm-F << EOF\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+printf \"\\n\" >> expect-rm-F\n+cat expect-F >> expect-rm-F\n+\n+test_expect_success 'verify note removal with -F /dev/null' '\n+\tgit log -4 > output &&\n+\ttest_cmp expect-rm-F output &&\n+\t! git notes show\n+'\n+\n+test_expect_success 'do not create empty note with -m \"\" (setup)' '\n+\tgit notes edit -m \"\"\n+'\n+\n+test_expect_success 'verify non-creation of note with -m \"\"' '\n+\tgit log -4 > output &&\n+\ttest_cmp expect-rm-F output &&\n+\t! git notes show\n+'\n+\n test_expect_success 'create other note on a different notes ref (setup)' '\n \t: > a6 &&\n \tgit add a6 &&\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134476","messageId":"1266096518-2104-18-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 17/30] builtin-notes: Add \"remove\" subcommand for removing existing notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:25Z","receivedAt":"2010-02-13T21:28:25Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Using \"git notes remove\" is equivalent to specifying an empty note message.\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   15 ++++++---\n builtin-notes.c             |   65 +++++++++++++++++++++++-------------------\n t/t3301-notes.sh            |   27 ++++++++++++++++++\n 3 files changed, 73 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 0d1ada6..a52d23a 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -8,14 +8,14 @@ git-notes - Add/inspect commit notes\n SYNOPSIS\n --------\n [verse]\n-'git notes' (edit [-F <file> | -m <msg>] | show) [commit]\n+'git notes' (edit [-F <file> | -m <msg>] | show | remove) [commit]\n \n DESCRIPTION\n -----------\n-This command allows you to add notes to commit messages, without\n-changing the commit.  To discern these notes from the message stored\n-in the commit object, the notes are indented like the message, after\n-an unindented line saying \"Notes:\".\n+This command allows you to add/remove notes to/from commit messages,\n+without changing the commit.  To discern these notes from the message\n+stored in the commit object, the notes are indented like the message,\n+after an unindented line saying \"Notes:\".\n \n To disable commit notes, you have to set the config variable\n core.notesRef to the empty string.  Alternatively, you can set it\n@@ -32,6 +32,11 @@ edit::\n show::\n \tShow the notes for a given commit (defaults to HEAD).\n \n+remove::\n+\tRemove the notes for a given commit (defaults to HEAD).\n+\tThis is equivalent to specifying an empty note message to\n+\tthe `edit` subcommand.\n+\n \n OPTIONS\n -------\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 7b4cb13..7c40075 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -20,6 +20,7 @@\n static const char * const git_notes_usage[] = {\n \t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes show [<object>]\",\n+\t\"git notes remove [<object>]\",\n \tNULL\n };\n \n@@ -197,9 +198,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tstruct notes_tree *t;\n \tunsigned char object[20], new_note[20];\n \tconst unsigned char *note;\n-\tconst char *object_ref, *logmsg;\n+\tconst char *object_ref;\n+\tchar logmsg[100];\n \n-\tint edit = 0, show = 0;\n+\tint edit = 0, show = 0, remove = 0;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n@@ -218,10 +220,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tedit = 1;\n \telse if (argc && !strcmp(argv[0], \"show\"))\n \t\tshow = 1;\n+\telse if (argc && !strcmp(argv[0], \"remove\"))\n+\t\tremove = 1;\n \n-\tif (edit + show != 1)\n+\tif (edit + show + remove != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n+\tif ((msg.given || msgfile) && !edit) {\n+\t\terror(\"cannot use -m/-F options with %s subcommand.\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n+\tif (msg.given && msgfile) {\n+\t\terror(\"mixing -m and -F options is not allowed.\");\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n \tobject_ref = argc == 2 ? argv[1] : \"HEAD\";\n \tif (argc > 2) {\n \t\terror(\"too many parameters\");\n@@ -236,7 +250,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tif (prefixcmp(t->ref, \"refs/notes/\"))\n \t\tdie(\"Refusing to %s notes in %s (outside of refs/notes/)\",\n-\t\t    edit ? \"edit\" : \"show\", t->ref);\n+\t\t    argv[0], t->ref);\n \n \tnote = get_note(t, object);\n \n@@ -250,35 +264,28 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\treturn execv_git_cmd(show_args);\n \t}\n \n-\t/* edit command */\n-\n-\tif (msg.given || msgfile) {\n-\t\tif (msg.given && msgfile) {\n-\t\t\terror(\"mixing -m and -F options is not allowed.\");\n-\t\t\tusage_with_options(git_notes_usage, options);\n-\t\t}\n-\t\tif (msg.given)\n-\t\t\tstrbuf_addbuf(&buf, &(msg.buf));\n-\t\telse {\n-\t\t\tif (!strcmp(msgfile, \"-\")) {\n-\t\t\t\tif (strbuf_read(&buf, 0, 1024) < 0)\n-\t\t\t\t\tdie_errno(\"cannot read '%s'\", msgfile);\n-\t\t\t} else {\n-\t\t\t\tif (strbuf_read_file(&buf, msgfile, 1024) < 0)\n-\t\t\t\t\tdie_errno(\"could not open or read '%s'\",\n-\t\t\t\t\t\tmsgfile);\n-\t\t\t}\n-\t\t}\n+\t/* edit/remove command */\n+\n+\tif (remove)\n+\t\tstrbuf_reset(&buf);\n+\telse if (msg.given)\n+\t\tstrbuf_addbuf(&buf, &(msg.buf));\n+\telse if (msgfile) {\n+\t\tif (!strcmp(msgfile, \"-\")) {\n+\t\t\tif (strbuf_read(&buf, 0, 1024) < 0)\n+\t\t\t\tdie_errno(\"cannot read '%s'\", msgfile);\n+\t\t} else if (strbuf_read_file(&buf, msgfile, 1024) < 0)\n+\t\t\tdie_errno(\"could not open or read '%s'\", msgfile);\n \t}\n \n-\tcreate_note(object, &buf, msg.given || msgfile, note, new_note);\n-\tif (is_null_sha1(new_note)) {\n+\tcreate_note(object, &buf, msg.given || msgfile || remove, note,\n+\t\t    new_note);\n+\tif (is_null_sha1(new_note))\n \t\tremove_note(t, object);\n-\t\tlogmsg = \"Note removed by 'git notes edit'\";\n-\t} else {\n+\telse\n \t\tadd_note(t, object, new_note, combine_notes_overwrite);\n-\t\tlogmsg = \"Note added by 'git notes edit'\";\n-\t}\n+\tsnprintf(logmsg, sizeof(logmsg), \"Note %s by 'git notes %s'\",\n+\t\t is_null_sha1(new_note) ? \"removed\" : \"added\", argv[0]);\n \tcommit_notes(t, logmsg);\n \n \tfree_notes(t);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex fe59e73..d29daac 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -265,6 +265,33 @@ test_expect_success 'verify non-creation of note with -m \"\"' '\n \t! git notes show\n '\n \n+test_expect_success 'remove note with \"git notes remove\" (setup)' '\n+\tgit notes remove HEAD^\n+'\n+\n+cat > expect-rm-remove << EOF\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+commit 15023535574ded8b1a89052b32673f84cf9582b8\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:16:13 2005 -0700\n+\n+    4th\n+EOF\n+\n+printf \"\\n\" >> expect-rm-remove\n+cat expect-multiline >> expect-rm-remove\n+\n+test_expect_success 'verify note removal with \"git notes remove\"' '\n+\tgit log -4 > output &&\n+\ttest_cmp expect-rm-remove output &&\n+\t! git notes show HEAD^\n+'\n+\n test_expect_success 'create other note on a different notes ref (setup)' '\n \t: > a6 &&\n \tgit add a6 &&\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134482","messageId":"1266096518-2104-19-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 18/30] t3305: Verify that removing notes triggers automatic fanout consolidation","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:26Z","receivedAt":"2010-02-13T21:28:26Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n t/t3305-notes-fanout.sh |   47 ++++++++++++++++++++++++++++++++++++++++++++++-\n 1 files changed, 46 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh\nindex 823b0ff..c6d263b 100755\n--- a/t/t3305-notes-fanout.sh\n+++ b/t/t3305-notes-fanout.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='Test that adding many notes triggers automatic fanout restructuring'\n+test_description='Test that adding/removing many notes triggers automatic fanout restructuring'\n \n . ./test-lib.sh\n \n@@ -47,4 +47,49 @@ test_expect_success 'many notes created with git-notes triggers fanout' '\n \tdone\n '\n \n+test_expect_success 'deleting most notes with git-notes' '\n+\tnum_notes=250 &&\n+\ti=0 &&\n+\tgit rev-list HEAD |\n+\twhile read sha1\n+\tdo\n+\t\ti=$(($i + 1)) &&\n+\t\tif test $i -gt $num_notes\n+\t\tthen\n+\t\t\tbreak\n+\t\tfi &&\n+\t\ttest_tick &&\n+\t\tgit notes remove \"$sha1\"\n+\tdone\n+'\n+\n+test_expect_success 'most notes deleted correctly with git-notes' '\n+\tgit log HEAD~250 | grep \"^    \" > output &&\n+\ti=50 &&\n+\twhile test $i -gt 0\n+\tdo\n+\t\techo \"    commit #$i\" &&\n+\t\techo \"    note #$i\" &&\n+\t\ti=$(($i - 1));\n+\tdone > expect &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'deleting most notes triggers fanout consolidation' '\n+\t# Expect entire notes tree to have a fanout == 0\n+\tgit ls-tree -r --name-only refs/notes/commits |\n+\twhile read path\n+\tdo\n+\t\tcase \"$path\" in\n+\t\t????????????????????????????????????????)\n+\t\t\t: true\n+\t\t\t;;\n+\t\t*)\n+\t\t\techo \"Invalid path \\\"$path\\\"\" &&\n+\t\t\treturn 1\n+\t\t\t;;\n+\t\tesac\n+\tdone\n+'\n+\n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134479","messageId":"1266096518-2104-20-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 19/30] Notes API: prune_notes(): Prune notes that belong to non-existing objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:27Z","receivedAt":"2010-02-13T21:28:27Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"When an object is made unreachable by Git, any notes that annotate that object\nare not automagically made unreachable, since all notes are always trivially\nreachable from a notes ref. In order to remove notes for non-existing objects,\nwe therefore need to add functionality for traversing the notes tree and\nexplicitly removing references to notes that annotate non-reachable objects.\nThus the notes objects themselves also become unreachable, and are removed\nby a later garbage collect.\n\nprune_notes() performs this traversal (by using for_each_note() internally),\nand removes the notes in question from the notes tree.\n\nNote that the effect of prune_notes() is not persistent unless a subsequent\ncall to write_notes_tree() is made.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   39 +++++++++++++++++++++++++++++++++++++++\n notes.h |   12 ++++++++++++\n 2 files changed, 51 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex d432517..3ba3e6d 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -749,6 +749,29 @@ static int write_each_note(const unsigned char *object_sha1,\n \t\twrite_each_note_helper(d->root, note_path, mode, note_sha1);\n }\n \n+struct note_delete_list {\n+\tstruct note_delete_list *next;\n+\tconst unsigned char *sha1;\n+};\n+\n+static int prune_notes_helper(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1, char *note_path,\n+\t\tvoid *cb_data)\n+{\n+\tstruct note_delete_list **l = (struct note_delete_list **) cb_data;\n+\tstruct note_delete_list *n;\n+\n+\tif (has_sha1_file(object_sha1))\n+\t\treturn 0; /* nothing to do for this note */\n+\n+\t/* failed to find object => prune this note */\n+\tn = (struct note_delete_list *) xmalloc(sizeof(*n));\n+\tn->next = *l;\n+\tn->sha1 = object_sha1;\n+\t*l = n;\n+\treturn 0;\n+}\n+\n int combine_notes_concatenate(unsigned char *cur_sha1,\n \t\tconst unsigned char *new_sha1)\n {\n@@ -922,6 +945,22 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result)\n \treturn ret;\n }\n \n+void prune_notes(struct notes_tree *t)\n+{\n+\tstruct note_delete_list *l = NULL;\n+\n+\tif (!t)\n+\t\tt = &default_notes_tree;\n+\tassert(t->initialized);\n+\n+\tfor_each_note(t, 0, prune_notes_helper, &l);\n+\n+\twhile (l) {\n+\t\tremove_note(t, l->sha1);\n+\t\tl = l->next;\n+\t}\n+}\n+\n void free_notes(struct notes_tree *t)\n {\n \tif (!t)\ndiff --git a/notes.h b/notes.h\nindex f98578f..bad03cc 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -162,6 +162,18 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,\n int write_notes_tree(struct notes_tree *t, unsigned char *result);\n \n /*\n+ * Remove all notes annotating non-existing objects from the given notes tree\n+ *\n+ * All notes in the given notes_tree that are associated with objects that no\n+ * longer exist in the database, are removed from the notes tree.\n+ *\n+ * IMPORTANT: The changes made by prune_notes() to the given notes_tree\n+ * structure are not persistent until a subsequent call to write_notes_tree()\n+ * returns zero.\n+ */\n+void prune_notes(struct notes_tree *t);\n+\n+/*\n  * Free (and de-initialize) the given notes_tree structure\n  *\n  * IMPORTANT: Changes made to the given notes_tree since the last, successful\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134492","messageId":"1266096518-2104-21-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 20/30] builtin-notes: Add \"prune\" subcommand for removing notes for missing objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:28Z","receivedAt":"2010-02-13T21:28:28Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"\"git notes prune\" will remove all notes that annotate unreachable/non-\nexisting objects.\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |    4 +-\n builtin-notes.c             |   28 ++++++++-----\n t/t3306-notes-prune.sh      |   94 +++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 115 insertions(+), 11 deletions(-)\n create mode 100755 t/t3306-notes-prune.sh\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex a52d23a..3973f90 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes\n SYNOPSIS\n --------\n [verse]\n-'git notes' (edit [-F <file> | -m <msg>] | show | remove) [commit]\n+'git notes' (edit [-F <file> | -m <msg>] | show | remove | prune) [commit]\n \n DESCRIPTION\n -----------\n@@ -37,6 +37,8 @@ remove::\n \tThis is equivalent to specifying an empty note message to\n \tthe `edit` subcommand.\n \n+prune::\n+\tRemove all notes for non-existing/unreachable objects.\n \n OPTIONS\n -------\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 7c40075..48bc455 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -21,6 +21,7 @@ static const char * const git_notes_usage[] = {\n \t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n+\t\"git notes prune\",\n \tNULL\n };\n \n@@ -201,7 +202,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tconst char *object_ref;\n \tchar logmsg[100];\n \n-\tint edit = 0, show = 0, remove = 0;\n+\tint edit = 0, show = 0, remove = 0, prune = 0;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n@@ -222,8 +223,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tshow = 1;\n \telse if (argc && !strcmp(argv[0], \"remove\"))\n \t\tremove = 1;\n+\telse if (argc && !strcmp(argv[0], \"prune\"))\n+\t\tprune = 1;\n \n-\tif (edit + show + remove != 1)\n+\tif (edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n \tif ((msg.given || msgfile) && !edit) {\n@@ -237,7 +240,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t}\n \n \tobject_ref = argc == 2 ? argv[1] : \"HEAD\";\n-\tif (argc > 2) {\n+\tif (argc > 2 || (prune && argc > 1)) {\n \t\terror(\"too many parameters\");\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n@@ -264,7 +267,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\treturn execv_git_cmd(show_args);\n \t}\n \n-\t/* edit/remove command */\n+\t/* edit/remove/prune command */\n \n \tif (remove)\n \t\tstrbuf_reset(&buf);\n@@ -278,12 +281,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\t\tdie_errno(\"could not open or read '%s'\", msgfile);\n \t}\n \n-\tcreate_note(object, &buf, msg.given || msgfile || remove, note,\n-\t\t    new_note);\n-\tif (is_null_sha1(new_note))\n-\t\tremove_note(t, object);\n-\telse\n-\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\tif (prune) {\n+\t\thashclr(new_note);\n+\t\tprune_notes(t);\n+\t} else {\n+\t\tcreate_note(object, &buf, msg.given || msgfile || remove, note,\n+\t\t\t    new_note);\n+\t\tif (is_null_sha1(new_note))\n+\t\t\tremove_note(t, object);\n+\t\telse\n+\t\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\t}\n \tsnprintf(logmsg, sizeof(logmsg), \"Note %s by 'git notes %s'\",\n \t\t is_null_sha1(new_note) ? \"removed\" : \"added\", argv[0]);\n \tcommit_notes(t, logmsg);\ndiff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh\nnew file mode 100755\nindex 0000000..b0adc7e\n--- /dev/null\n+++ b/t/t3306-notes-prune.sh\n@@ -0,0 +1,94 @@\n+#!/bin/sh\n+\n+test_description='Test git notes prune'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup: create a few commits with notes' '\n+\n+\t: > file1 &&\n+\tgit add file1 &&\n+\ttest_tick &&\n+\tgit commit -m 1st &&\n+\tgit notes edit -m \"Note #1\" &&\n+\t: > file2 &&\n+\tgit add file2 &&\n+\ttest_tick &&\n+\tgit commit -m 2nd &&\n+\tgit notes edit -m \"Note #2\" &&\n+\t: > file3 &&\n+\tgit add file3 &&\n+\ttest_tick &&\n+\tgit commit -m 3rd &&\n+\tgit notes edit -m \"Note #3\"\n+'\n+\n+cat > expect <<END_OF_LOG\n+commit 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:15:13 2005 -0700\n+\n+    3rd\n+\n+Notes:\n+    Note #3\n+\n+commit 08341ad9e94faa089d60fd3f523affb25c6da189\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:14:13 2005 -0700\n+\n+    2nd\n+\n+Notes:\n+    Note #2\n+\n+commit ab5f302035f2e7aaf04265f08b42034c23256e1f\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:13:13 2005 -0700\n+\n+    1st\n+\n+Notes:\n+    Note #1\n+END_OF_LOG\n+\n+test_expect_success 'verify commits and notes' '\n+\n+\tgit log > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'remove some commits' '\n+\n+\tgit reset --hard HEAD~2 &&\n+\tgit reflog expire --expire=now HEAD &&\n+\tgit gc --prune=now\n+'\n+\n+test_expect_success 'verify that commits are gone' '\n+\n+\t! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&\n+\t! git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 &&\n+\tgit cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f\n+'\n+\n+test_expect_success 'verify that notes are still present' '\n+\n+\tgit notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&\n+\tgit notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&\n+\tgit notes show ab5f302035f2e7aaf04265f08b42034c23256e1f\n+'\n+\n+test_expect_success 'prune notes' '\n+\n+\tgit notes prune\n+'\n+\n+test_expect_success 'verify that notes are gone' '\n+\n+\t! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 &&\n+\t! git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 &&\n+\tgit notes show ab5f302035f2e7aaf04265f08b42034c23256e1f\n+'\n+\n+test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134490","messageId":"1266096518-2104-22-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 21/30] Documentation: Generalize git-notes docs to 'objects' instead of 'commits'","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:29Z","receivedAt":"2010-02-13T21:28:29Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Notes can annotate arbitrary objects (not only commits), but this is not\nreflected in the current documentation.\n\nThis patch rewrites the git-notes documentation to talk about 'objects'\ninstead of 'commits'. However, the discussion on commit notes and how\nthey are displayed by 'git log' is largely preserved.\n\nFinally, I add myself to the Author/Documentation credits, since most of\nthe lines in the git-notes code and docs are blamed on me.\n\nCc: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   35 ++++++++++++++++++++---------------\n 1 files changed, 20 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 3973f90..84db2a4 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -3,37 +3,41 @@ git-notes(1)\n \n NAME\n ----\n-git-notes - Add/inspect commit notes\n+git-notes - Add/inspect object notes\n \n SYNOPSIS\n --------\n [verse]\n-'git notes' (edit [-F <file> | -m <msg>] | show | remove | prune) [commit]\n+'git notes' (edit [-F <file> | -m <msg>] | show | remove | prune) [object]\n \n DESCRIPTION\n -----------\n-This command allows you to add/remove notes to/from commit messages,\n-without changing the commit.  To discern these notes from the message\n-stored in the commit object, the notes are indented like the message,\n-after an unindented line saying \"Notes:\".\n+This command allows you to add/remove notes to/from objects, without\n+changing the objects themselves.\n \n-To disable commit notes, you have to set the config variable\n-core.notesRef to the empty string.  Alternatively, you can set it\n-to a different ref, something like \"refs/notes/bugzilla\".  This setting\n-can be overridden by the environment variable \"GIT_NOTES_REF\".\n+A typical use of notes is to extend a commit message without having\n+to change the commit itself. Such commit notes can be shown by `git log`\n+along with the original commit message. To discern these notes from the\n+message stored in the commit object, the notes are indented like the\n+message, after an unindented line saying \"Notes:\".\n+\n+To disable notes, you have to set the config variable core.notesRef to\n+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 \n SUBCOMMANDS\n -----------\n \n edit::\n-\tEdit the notes for a given commit (defaults to HEAD).\n+\tEdit the notes for a given object (defaults to HEAD).\n \n show::\n-\tShow the notes for a given commit (defaults to HEAD).\n+\tShow the notes for a given object (defaults to HEAD).\n \n remove::\n-\tRemove the notes for a given commit (defaults to HEAD).\n+\tRemove the notes for a given object (defaults to HEAD).\n \tThis is equivalent to specifying an empty note message to\n \tthe `edit` subcommand.\n \n@@ -54,11 +58,12 @@ OPTIONS\n \n Author\n ------\n-Written by Johannes Schindelin <johannes.schindelin@gmx.de>\n+Written by Johannes Schindelin <johannes.schindelin@gmx.de> and\n+Johan Herland <johan@herland.net>\n \n Documentation\n -------------\n-Documentation by Johannes Schindelin\n+Documentation by Johannes Schindelin and Johan Herland\n \n GIT\n ---\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134478","messageId":"1266096518-2104-23-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 22/30] builtin-notes: Add \"list\" subcommand for listing note objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:30Z","receivedAt":"2010-02-13T21:28:30Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"\"git notes list\" will list all note objects in the current notes ref (in the\nformat \"<note object> <annotated object>\"). \"git notes list <object>\" will\nlist the note object associated with the given <object>, or fail loudly if\nthe given <object> has no associated notes.\n\nIf no arguments are given to \"git notes\", it defaults to the \"list\"\nsubcommand. This is for pseudo-compatibility with \"git tag\" and \"git branch\".\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   13 ++++++++++++-\n builtin-notes.c             |   37 ++++++++++++++++++++++++++++++++-----\n t/t3301-notes.sh            |   32 ++++++++++++++++++++++++++++++++\n 3 files changed, 76 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 84db2a4..4d29d5f 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -8,7 +8,12 @@ git-notes - Add/inspect object notes\n SYNOPSIS\n --------\n [verse]\n-'git notes' (edit [-F <file> | -m <msg>] | show | remove | prune) [object]\n+'git notes' [list [<object>]]\n+'git notes' edit [-F <file> | -m <msg>] [<object>]\n+'git notes' show [<object>]\n+'git notes' remove [<object>]\n+'git notes' prune\n+\n \n DESCRIPTION\n -----------\n@@ -30,6 +35,12 @@ by the environment variable \"GIT_NOTES_REF\".\n SUBCOMMANDS\n -----------\n \n+list::\n+\tList the notes object for a given object. If no object is\n+\tgiven, show a list of all note objects and the objects they\n+\tannotate (in the format \"<note object> <annotated object>\").\n+\tThis is the default subcommand if no subcommand is given.\n+\n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n \ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 48bc455..b808534 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -18,6 +18,7 @@\n #include \"parse-options.h\"\n \n static const char * const git_notes_usage[] = {\n+\t\"git notes [list [<object>]]\",\n \t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n@@ -31,6 +32,14 @@ static const char note_template[] =\n \t\"# Write/edit the notes for the following object:\\n\"\n \t\"#\\n\";\n \n+static int list_each_note(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1, char *note_path,\n+\t\tvoid *cb_data)\n+{\n+\tprintf(\"%s %s\\n\", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1));\n+\treturn 0;\n+}\n+\n static void write_note_data(int fd, const unsigned char *sha1)\n {\n \tunsigned long size;\n@@ -202,7 +211,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tconst char *object_ref;\n \tchar logmsg[100];\n \n-\tint edit = 0, show = 0, remove = 0, prune = 0;\n+\tint list = 0, edit = 0, show = 0, remove = 0, prune = 0;\n+\tint given_object;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n@@ -217,7 +227,9 @@ 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 (argc && !strcmp(argv[0], \"edit\"))\n+\tif (argc && !strcmp(argv[0], \"list\"))\n+\t\tlist = 1;\n+\telse if (argc && !strcmp(argv[0], \"edit\"))\n \t\tedit = 1;\n \telse if (argc && !strcmp(argv[0], \"show\"))\n \t\tshow = 1;\n@@ -225,8 +237,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tremove = 1;\n \telse if (argc && !strcmp(argv[0], \"prune\"))\n \t\tprune = 1;\n+\telse if (!argc)\n+\t\tlist = 1; /* Default to 'list' if no other subcommand given */\n \n-\tif (edit + show + remove + prune != 1)\n+\tif (list + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n \tif ((msg.given || msgfile) && !edit) {\n@@ -239,7 +253,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n \n-\tobject_ref = argc == 2 ? argv[1] : \"HEAD\";\n+\tgiven_object = argc == 2;\n+\tobject_ref = given_object ? argv[1] : \"HEAD\";\n \tif (argc > 2 || (prune && argc > 1)) {\n \t\terror(\"too many parameters\");\n \t\tusage_with_options(git_notes_usage, options);\n@@ -257,9 +272,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tnote = get_note(t, object);\n \n+\t/* list command */\n+\n+\tif (list) {\n+\t\tif (given_object) {\n+\t\t\tif (note) {\n+\t\t\t\tputs(sha1_to_hex(note));\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t} else\n+\t\t\treturn for_each_note(t, 0, list_each_note, NULL);\n+\t}\n+\n \t/* show command */\n \n-\tif (show && !note) {\n+\tif ((list || show) && !note) {\n \t\terror(\"No note found for object %s.\", sha1_to_hex(object));\n \t\treturn 1;\n \t} else if (show) {\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex d29daac..768a1cb 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -292,6 +292,38 @@ test_expect_success 'verify note removal with \"git notes remove\"' '\n \t! git notes show HEAD^\n '\n \n+cat > expect << EOF\n+c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75\n+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5\n+EOF\n+\n+test_expect_success 'list notes with \"git notes list\"' '\n+\tgit notes list > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'list notes with \"git notes\"' '\n+\tgit notes > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+c18dc024e14f08d18d14eea0d747ff692d66d6a3\n+EOF\n+\n+test_expect_success 'list specific note with \"git notes list <object>\"' '\n+\tgit notes list HEAD^^ > output &&\n+\ttest_cmp expect output\n+'\n+\n+cat > expect << EOF\n+EOF\n+\n+test_expect_success 'listing non-existing notes fails' '\n+\ttest_must_fail git notes list HEAD > output &&\n+\ttest_cmp expect output\n+'\n+\n test_expect_success 'create other note on a different notes ref (setup)' '\n \t: > a6 &&\n \tgit add a6 &&\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134489","messageId":"1266096518-2104-24-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 23/30] builtin-notes: Add --message/--file aliases for -m/-F options","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:31Z","receivedAt":"2010-02-13T21:28:31Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |    2 ++\n builtin-notes.c             |    4 ++--\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 4d29d5f..8969f6f 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -58,11 +58,13 @@ prune::\n OPTIONS\n -------\n -m <msg>::\n+--message=<msg>::\n \tUse the given note message (instead of prompting).\n \tIf multiple `-m` options are given, their values\n \tare concatenated as separate paragraphs.\n \n -F <file>::\n+--file=<file>::\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n \ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex b808534..ec959bc 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -217,9 +217,9 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes edit options\"),\n-\t\tOPT_CALLBACK('m', NULL, &msg, \"msg\",\n+\t\tOPT_CALLBACK('m', \"message\", &msg, \"msg\",\n \t\t\t     \"note contents as a string\", parse_msg_arg),\n-\t\tOPT_FILENAME('F', NULL, &msgfile, \"note contents in a file\"),\n+\t\tOPT_FILENAME('F', \"file\", &msgfile, \"note contents in a file\"),\n \t\tOPT_END()\n \t};\n \n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134495","messageId":"1266096518-2104-25-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 24/30] builtin-notes: Add \"add\" subcommand for adding notes to objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:32Z","receivedAt":"2010-02-13T21:28:32Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"\"git notes add\" is identical to \"git notes edit\" except that instead of\nediting existing notes for a given object, you can only add notes to an\nobject that currently has none. If \"git notes add\" finds existing notes\nfor the given object, the addition is aborted. However, if the new\n-f/--force option is used, \"git notes add\" will _overwrite_ the existing\nnotes with the new notes contents.\n\nIf there is no existing notes for the given object. \"git notes add\" is\nidentical to \"git notes edit\" (i.e. it adds a new note).\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSuggested-by: Joey Hess <joey@kitenet.net>\nImproved-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   11 ++++++++\n builtin-notes.c             |   30 ++++++++++++++++++++---\n t/t3301-notes.sh            |   55 +++++++++++++++++++++++++++++--------------\n 3 files changed, 74 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 8969f6f..94e12b5 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n+'git notes' add [-f] [-F <file> | -m <msg>] [<object>]\n 'git notes' edit [-F <file> | -m <msg>] [<object>]\n 'git notes' show [<object>]\n 'git notes' remove [<object>]\n@@ -41,6 +42,11 @@ list::\n \tannotate (in the format \"<note object> <annotated object>\").\n \tThis is the default subcommand if no subcommand is given.\n \n+add::\n+\tAdd notes for a given object (defaults to HEAD). Abort if the\n+\tobject already has notes, abort. (use `-f` to overwrite an\n+\texisting note).\n+\n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n \n@@ -57,6 +63,11 @@ prune::\n \n OPTIONS\n -------\n+-f::\n+--force::\n+\tWhen adding notes to an object that already has notes,\n+\toverwrite the existing notes (instead of aborting).\n+\n -m <msg>::\n --message=<msg>::\n \tUse the given note message (instead of prompting).\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex ec959bc..006edf6 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -19,6 +19,7 @@\n \n static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n+\t\"git notes add [-f] [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n@@ -211,7 +212,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tconst char *object_ref;\n \tchar logmsg[100];\n \n-\tint list = 0, edit = 0, show = 0, remove = 0, prune = 0;\n+\tint list = 0, add = 0, edit = 0, show = 0, remove = 0, prune = 0,\n+\t    force = 0;\n \tint given_object;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n@@ -220,6 +222,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tOPT_CALLBACK('m', \"message\", &msg, \"msg\",\n \t\t\t     \"note contents as a string\", parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, \"note contents in a file\"),\n+\t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n \t\tOPT_END()\n \t};\n \n@@ -229,6 +232,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tif (argc && !strcmp(argv[0], \"list\"))\n \t\tlist = 1;\n+\telse if (argc && !strcmp(argv[0], \"add\"))\n+\t\tadd = 1;\n \telse if (argc && !strcmp(argv[0], \"edit\"))\n \t\tedit = 1;\n \telse if (argc && !strcmp(argv[0], \"show\"))\n@@ -240,10 +245,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \telse if (!argc)\n \t\tlist = 1; /* Default to 'list' if no other subcommand given */\n \n-\tif (list + edit + show + remove + prune != 1)\n+\tif (list + add + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n-\tif ((msg.given || msgfile) && !edit) {\n+\tif ((msg.given || msgfile) && !(add || edit)) {\n \t\terror(\"cannot use -m/-F options with %s subcommand.\", argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n@@ -253,6 +258,11 @@ 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 (force && !add) {\n+\t\terror(\"cannot use -f option with %s subcommand.\", argv[0]);\n+\t\tusage_with_options(git_notes_usage, options);\n+\t}\n+\n \tgiven_object = argc == 2;\n \tobject_ref = given_object ? argv[1] : \"HEAD\";\n \tif (argc > 2 || (prune && argc > 1)) {\n@@ -294,7 +304,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\treturn execv_git_cmd(show_args);\n \t}\n \n-\t/* edit/remove/prune command */\n+\t/* add/edit/remove/prune command */\n+\n+\tif (add && note) {\n+\t\tif (force)\n+\t\t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n+\t\t\t\tsha1_to_hex(object));\n+\t\telse {\n+\t\t\terror(\"Cannot add notes. Found existing notes for object %s. \"\n+\t\t\t      \"Use '-f' to overwrite existing notes\",\n+\t\t\t      sha1_to_hex(object));\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n \n \tif (remove)\n \t\tstrbuf_reset(&buf);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 768a1cb..df458ca 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -16,7 +16,7 @@ GIT_EDITOR=./fake_editor.sh\n export GIT_EDITOR\n \n test_expect_success 'cannot annotate non-existing HEAD' '\n-\t(MSG=3 && export MSG && test_must_fail git notes edit)\n+\t(MSG=3 && export MSG && test_must_fail git notes add)\n '\n \n test_expect_success setup '\n@@ -32,18 +32,18 @@ test_expect_success setup '\n \n test_expect_success 'need valid notes ref' '\n \t(MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&\n-\t test_must_fail git notes edit) &&\n+\t test_must_fail git notes add) &&\n \t(MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&\n \t test_must_fail git notes show)\n '\n \n-test_expect_success 'refusing to edit in refs/heads/' '\n+test_expect_success 'refusing to add notes in refs/heads/' '\n \t(MSG=1 GIT_NOTES_REF=refs/heads/bogus &&\n \t export MSG GIT_NOTES_REF &&\n-\t test_must_fail git notes edit)\n+\t test_must_fail git notes add)\n '\n \n-test_expect_success 'refusing to edit in refs/remotes/' '\n+test_expect_success 'refusing to edit notes in refs/remotes/' '\n \t(MSG=1 GIT_NOTES_REF=refs/remotes/bogus &&\n \t export MSG GIT_NOTES_REF &&\n \t test_must_fail git notes edit)\n@@ -56,16 +56,34 @@ test_expect_success 'handle empty notes gracefully' '\n \n test_expect_success 'create notes' '\n \tgit config core.notesRef refs/notes/commits &&\n-\tMSG=b0 git notes edit &&\n+\tMSG=b4 git notes add &&\n \ttest ! -f .git/NOTES_EDITMSG &&\n \ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n-\ttest b0 = $(git notes show) &&\n+\ttest b4 = $(git notes show) &&\n \tgit show HEAD^ &&\n \ttest_must_fail git notes show HEAD^\n '\n \n test_expect_success 'edit existing notes' '\n-\tMSG=b1 git notes edit &&\n+\tMSG=b3 git notes edit &&\n+\ttest ! -f .git/NOTES_EDITMSG &&\n+\ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n+\ttest b3 = $(git notes show) &&\n+\tgit show HEAD^ &&\n+\ttest_must_fail git notes show HEAD^\n+'\n+\n+test_expect_success 'cannot add note where one exists' '\n+\t! MSG=b2 git notes add &&\n+\ttest ! -f .git/NOTES_EDITMSG &&\n+\ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n+\ttest b3 = $(git notes show) &&\n+\tgit show HEAD^ &&\n+\ttest_must_fail git notes show HEAD^\n+'\n+\n+test_expect_success 'can overwrite existing note with \"git notes add -f\"' '\n+\tMSG=b1 git notes add -f &&\n \ttest ! -f .git/NOTES_EDITMSG &&\n \ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n \ttest b1 = $(git notes show) &&\n@@ -89,6 +107,7 @@ test_expect_success 'show notes' '\n \tgit log -1 > output &&\n \ttest_cmp expect output\n '\n+\n test_expect_success 'create multi-line notes (setup)' '\n \t: > a3 &&\n \tgit add a3 &&\n@@ -96,7 +115,7 @@ test_expect_success 'create multi-line notes (setup)' '\n \tgit commit -m 3rd &&\n \tMSG=\"b3\n c3c3c3c3\n-d3d3d3\" git notes edit\n+d3d3d3\" git notes add\n '\n \n cat > expect-multiline << EOF\n@@ -125,7 +144,7 @@ test_expect_success 'create -F notes (setup)' '\n \ttest_tick &&\n \tgit commit -m 4th &&\n \techo \"xyzzy\" > note5 &&\n-\tgit notes edit -F note5\n+\tgit notes add -F note5\n '\n \n cat > expect-F << EOF\n@@ -205,7 +224,7 @@ test_expect_success 'create -m notes (setup)' '\n \tgit add a5 &&\n \ttest_tick &&\n \tgit commit -m 5th &&\n-\tgit notes edit -m spam -m \"foo\n+\tgit notes add -m spam -m \"foo\n bar\n baz\"\n '\n@@ -234,8 +253,8 @@ test_expect_success 'show -m notes' '\n \ttest_cmp expect-m output\n '\n \n-test_expect_success 'remove note with -F /dev/null (setup)' '\n-\tgit notes edit -F /dev/null\n+test_expect_success 'remove note with add -f -F /dev/null (setup)' '\n+\tgit notes add -f -F /dev/null\n '\n \n cat > expect-rm-F << EOF\n@@ -256,7 +275,7 @@ test_expect_success 'verify note removal with -F /dev/null' '\n '\n \n test_expect_success 'do not create empty note with -m \"\" (setup)' '\n-\tgit notes edit -m \"\"\n+\tgit notes add -m \"\"\n '\n \n test_expect_success 'verify non-creation of note with -m \"\"' '\n@@ -329,7 +348,7 @@ test_expect_success 'create other note on a different notes ref (setup)' '\n \tgit add a6 &&\n \ttest_tick &&\n \tgit commit -m 6th &&\n-\tGIT_NOTES_REF=\"refs/notes/other\" git notes edit -m \"other note\"\n+\tGIT_NOTES_REF=\"refs/notes/other\" git notes add -m \"other note\"\n '\n \n cat > expect-other << EOF\n@@ -374,17 +393,17 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '\n \n test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '\n \techo \"Note on a tree\" > expect\n-\tgit notes edit -m \"Note on a tree\" HEAD: &&\n+\tgit notes add -m \"Note on a tree\" HEAD: &&\n \tgit notes show HEAD: > actual &&\n \ttest_cmp expect actual &&\n \techo \"Note on a blob\" > expect\n \tfilename=$(git ls-tree --name-only HEAD | head -n1) &&\n-\tgit notes edit -m \"Note on a blob\" HEAD:$filename &&\n+\tgit notes add -m \"Note on a blob\" HEAD:$filename &&\n \tgit notes show HEAD:$filename > actual &&\n \ttest_cmp expect actual &&\n \techo \"Note on a tag\" > expect\n \tgit tag -a -m \"This is an annotated tag\" foobar HEAD^ &&\n-\tgit notes edit -m \"Note on a tag\" foobar &&\n+\tgit notes add -m \"Note on a tag\" foobar &&\n \tgit notes show foobar > actual &&\n \ttest_cmp expect actual\n '\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134487","messageId":"1266096518-2104-26-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 25/30] builtin-notes: Add \"append\" subcommand for appending to note objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:33Z","receivedAt":"2010-02-13T21:28:33Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"\"git notes append\" is equivalent to \"git notes edit\" except that instead\nof editing existing notes contents, you can only append to it. This is\nuseful for quickly adding annotations like e.g.:\n\tgit notes append -m \"Acked-by: A U Thor <author@example.com>\"\n\n\"git notes append\" takes the same -m/-F options as \"git notes add\".\n\nIf there is no existing note to append to, \"git notes append\" is identical\nto \"git notes add\" (i.e. it adds a new note).\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |    5 +++++\n builtin-notes.c             |   35 ++++++++++++++++++++++++++---------\n t/t3301-notes.sh            |   36 ++++++++++++++++++++++++++++++++++++\n 3 files changed, 67 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 94e12b5..35dd8fa 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git notes' [list [<object>]]\n 'git notes' add [-f] [-F <file> | -m <msg>] [<object>]\n+'git notes' append [-F <file> | -m <msg>] [<object>]\n 'git notes' edit [-F <file> | -m <msg>] [<object>]\n 'git notes' show [<object>]\n 'git notes' remove [<object>]\n@@ -47,6 +48,10 @@ add::\n \tobject already has notes, abort. (use `-f` to overwrite an\n \texisting note).\n \n+append::\n+\tAppend to the notes of an existing object (defaults to HEAD).\n+\tCreates a new notes object if needed.\n+\n edit::\n \tEdit the notes for a given object (defaults to HEAD).\n \ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 006edf6..c88df00 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -20,6 +20,7 @@\n static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n \t\"git notes add [-f] [-m <msg> | -F <file>] [<object>]\",\n+\t\"git notes append [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n@@ -94,7 +95,7 @@ static void write_commented_object(int fd, const unsigned char *object)\n \n static void create_note(const unsigned char *object,\n \t\t\tstruct strbuf *buf,\n-\t\t\tint skip_editor,\n+\t\t\tint skip_editor, int append_only,\n \t\t\tconst unsigned char *prev,\n \t\t\tunsigned char *result)\n {\n@@ -109,7 +110,7 @@ static void create_note(const unsigned char *object,\n \t\tif (fd < 0)\n \t\t\tdie_errno(\"could not create file '%s'\", path);\n \n-\t\tif (prev)\n+\t\tif (prev && !append_only)\n \t\t\twrite_note_data(fd, prev);\n \t\twrite_or_die(fd, note_template, strlen(note_template));\n \n@@ -125,6 +126,20 @@ static void create_note(const unsigned char *object,\n \n \tstripspace(buf, 1);\n \n+\tif (prev && append_only) {\n+\t\t/* Append buf to previous note contents */\n+\t\tunsigned long size;\n+\t\tenum object_type type;\n+\t\tchar *prev_buf = read_sha1_file(prev, &type, &size);\n+\n+\t\tstrbuf_grow(buf, size + 1);\n+\t\tif (buf->len && prev_buf && size)\n+\t\t\tstrbuf_insert(buf, 0, \"\\n\", 1);\n+\t\tif (prev_buf && size)\n+\t\t\tstrbuf_insert(buf, 0, prev_buf, size);\n+\t\tfree(prev_buf);\n+\t}\n+\n \tif (!buf->len) {\n \t\tfprintf(stderr, \"Removing note for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n@@ -212,8 +227,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tconst char *object_ref;\n \tchar logmsg[100];\n \n-\tint list = 0, add = 0, edit = 0, show = 0, remove = 0, prune = 0,\n-\t    force = 0;\n+\tint list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,\n+\t    prune = 0, force = 0;\n \tint given_object;\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n@@ -234,6 +249,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tlist = 1;\n \telse if (argc && !strcmp(argv[0], \"add\"))\n \t\tadd = 1;\n+\telse if (argc && !strcmp(argv[0], \"append\"))\n+\t\tappend = 1;\n \telse if (argc && !strcmp(argv[0], \"edit\"))\n \t\tedit = 1;\n \telse if (argc && !strcmp(argv[0], \"show\"))\n@@ -245,10 +262,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \telse if (!argc)\n \t\tlist = 1; /* Default to 'list' if no other subcommand given */\n \n-\tif (list + add + edit + show + remove + prune != 1)\n+\tif (list + add + append + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n-\tif ((msg.given || msgfile) && !(add || edit)) {\n+\tif ((msg.given || msgfile) && !(add || append || edit)) {\n \t\terror(\"cannot use -m/-F options with %s subcommand.\", argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n@@ -304,7 +321,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\treturn execv_git_cmd(show_args);\n \t}\n \n-\t/* add/edit/remove/prune command */\n+\t/* add/append/edit/remove/prune command */\n \n \tif (add && note) {\n \t\tif (force)\n@@ -334,8 +351,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\thashclr(new_note);\n \t\tprune_notes(t);\n \t} else {\n-\t\tcreate_note(object, &buf, msg.given || msgfile || remove, note,\n-\t\t\t    new_note);\n+\t\tcreate_note(object, &buf, msg.given || msgfile || remove,\n+\t\t\t    append, note, new_note);\n \t\tif (is_null_sha1(new_note))\n \t\t\tremove_note(t, object);\n \t\telse\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex df458ca..290ed63 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -343,6 +343,42 @@ test_expect_success 'listing non-existing notes fails' '\n \ttest_cmp expect output\n '\n \n+cat > expect << EOF\n+Initial set of notes\n+\n+More notes appended with git notes append\n+EOF\n+\n+test_expect_success 'append to existing note with \"git notes append\"' '\n+\tgit notes add -m \"Initial set of notes\" &&\n+\tgit notes append -m \"More notes appended with git notes append\" &&\n+\tgit notes show > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'appending empty string does not change existing note' '\n+\tgit notes append -m \"\" &&\n+\tgit notes show > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'git notes append == add when there is no existing note' '\n+\tgit notes remove HEAD &&\n+\ttest_must_fail git notes list HEAD &&\n+\tgit notes append -m \"Initial set of notes\n+\n+More notes appended with git notes append\" &&\n+\tgit notes show > output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'appending empty string to non-existing note does not create note' '\n+\tgit notes remove HEAD &&\n+\ttest_must_fail git notes list HEAD &&\n+\tgit notes append -m \"\" &&\n+\ttest_must_fail git notes list HEAD\n+'\n+\n test_expect_success 'create other note on a different notes ref (setup)' '\n \t: > a6 &&\n \tgit add a6 &&\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134491","messageId":"1266096518-2104-27-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 26/30] builtin-notes: Deprecate the -m/-F options for \"git notes edit\"","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:34Z","receivedAt":"2010-02-13T21:28:34Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The semantics for \"git notes edit -m/-F\" overlap with those for\n\"git notes add -f\", and the behaviour (i.e. overwriting existing\nnotes with the given message/file) is more intuitively captured\nby (and better documented with) \"git notes add -f\".\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |    2 +-\n builtin-notes.c             |   10 ++++++++--\n t/t3304-notes-mixed.sh      |    2 +-\n t/t3305-notes-fanout.sh     |    2 +-\n t/t3306-notes-prune.sh      |    6 +++---\n 5 files changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 35dd8fa..53c5d90 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -11,7 +11,7 @@ SYNOPSIS\n 'git notes' [list [<object>]]\n 'git notes' add [-f] [-F <file> | -m <msg>] [<object>]\n 'git notes' append [-F <file> | -m <msg>] [<object>]\n-'git notes' edit [-F <file> | -m <msg>] [<object>]\n+'git notes' edit [<object>]\n 'git notes' show [<object>]\n 'git notes' remove [<object>]\n 'git notes' prune\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex c88df00..572b477 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -21,7 +21,7 @@ static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n \t\"git notes add [-f] [-m <msg> | -F <file>] [<object>]\",\n \t\"git notes append [-m <msg> | -F <file>] [<object>]\",\n-\t\"git notes edit [-m <msg> | -F <file>] [<object>]\",\n+\t\"git notes edit [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n \t\"git notes prune\",\n@@ -233,7 +233,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n-\t\tOPT_GROUP(\"Notes edit options\"),\n+\t\tOPT_GROUP(\"Notes options\"),\n \t\tOPT_CALLBACK('m', \"message\", &msg, \"msg\",\n \t\t\t     \"note contents as a string\", parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, \"note contents in a file\"),\n@@ -270,6 +270,12 @@ 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 ((msg.given || msgfile) && edit) {\n+\t\tfprintf(stderr, \"The -m and -F options has been deprecated for\"\n+\t\t\t\" the 'edit' subcommand.\\n\"\n+\t\t\t\"Please use 'git notes add -f -m/-F' instead.\\n\");\n+\t}\n+\n \tif (msg.given && msgfile) {\n \t\terror(\"mixing -m and -F options is not allowed.\");\n \t\tusage_with_options(git_notes_usage, options);\ndiff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh\nindex c975a6d..1709e8c 100755\n--- a/t/t3304-notes-mixed.sh\n+++ b/t/t3304-notes-mixed.sh\n@@ -188,7 +188,7 @@ test_expect_success \"verify contents of non-notes\" '\n test_expect_success \"git-notes preserves non-notes\" '\n\n \ttest_tick &&\n-\tgit notes edit -m \"foo bar\"\n+\tgit notes add -f -m \"foo bar\"\n '\n\n test_expect_success \"verify contents of non-notes after git-notes\" '\ndiff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh\nindex c6d263b..b1ea64b 100755\n--- a/t/t3305-notes-fanout.sh\n+++ b/t/t3305-notes-fanout.sh\n@@ -14,7 +14,7 @@ test_expect_success 'creating many notes with git-notes' '\n \t\techo \"file for commit #$i\" > file &&\n \t\tgit add file &&\n \t\tgit commit -q -m \"commit #$i\" &&\n-\t\tgit notes edit -m \"note #$i\" || return 1\n+\t\tgit notes add -m \"note #$i\" || return 1\n \tdone\n '\n\ndiff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh\nindex b0adc7e..a0ed035 100755\n--- a/t/t3306-notes-prune.sh\n+++ b/t/t3306-notes-prune.sh\n@@ -10,17 +10,17 @@ test_expect_success 'setup: create a few commits with notes' '\n \tgit add file1 &&\n \ttest_tick &&\n \tgit commit -m 1st &&\n-\tgit notes edit -m \"Note #1\" &&\n+\tgit notes add -m \"Note #1\" &&\n \t: > file2 &&\n \tgit add file2 &&\n \ttest_tick &&\n \tgit commit -m 2nd &&\n-\tgit notes edit -m \"Note #2\" &&\n+\tgit notes add -m \"Note #2\" &&\n \t: > file3 &&\n \tgit add file3 &&\n \ttest_tick &&\n \tgit commit -m 3rd &&\n-\tgit notes edit -m \"Note #3\"\n+\tgit notes add -m \"Note #3\"\n '\n\n cat > expect <<END_OF_LOG\n--\n1.7.0.rc1.141.gd3fd\n"},{"id":"134484","messageId":"1266096518-2104-28-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 27/30] builtin-notes: Refactor handling of -F option to allow combining -m and -F","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:35Z","receivedAt":"2010-02-13T21:28:35Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"By moving the -F option handling into a separate function (parse_file_arg),\nwe can start allowing several -F options, and mixed usage of -m and -F\noptions. Each -m/-F given appends to the note message, in the order they are\ngiven on the command-line.\n\nThe patch includes tests verifying correct behaviour of the new functionality.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n builtin-notes.c  |   94 +++++++++++++++++++++++++++++------------------------\n t/t3301-notes.sh |   23 ++++++++++++-\n 2 files changed, 73 insertions(+), 44 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 572b477..190c46c 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -34,6 +34,11 @@ static const char note_template[] =\n \t\"# Write/edit the notes for the following object:\\n\"\n \t\"#\\n\";\n \n+struct msg_arg {\n+\tint given;\n+\tstruct strbuf buf;\n+};\n+\n static int list_each_note(const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1, char *note_path,\n \t\tvoid *cb_data)\n@@ -93,15 +98,13 @@ static void write_commented_object(int fd, const unsigned char *object)\n \t\t    sha1_to_hex(object));\n }\n \n-static void create_note(const unsigned char *object,\n-\t\t\tstruct strbuf *buf,\n-\t\t\tint skip_editor, int append_only,\n-\t\t\tconst unsigned char *prev,\n+static void create_note(const unsigned char *object, struct msg_arg *msg,\n+\t\t\tint append_only, const unsigned char *prev,\n \t\t\tunsigned char *result)\n {\n \tchar *path = NULL;\n \n-\tif (!skip_editor) {\n+\tif (!msg->given) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n@@ -118,34 +121,33 @@ static void create_note(const unsigned char *object,\n \n \t\tclose(fd);\n \n-\t\tif (launch_editor(path, buf, NULL)) {\n+\t\tif (launch_editor(path, &(msg->buf), NULL)) {\n \t\t\tdie(\"Please supply the note contents using either -m\" \\\n \t\t\t    \" or -F option\");\n \t\t}\n+\t\tstripspace(&(msg->buf), 1);\n \t}\n \n-\tstripspace(buf, 1);\n-\n \tif (prev && append_only) {\n \t\t/* Append buf to previous note contents */\n \t\tunsigned long size;\n \t\tenum object_type type;\n \t\tchar *prev_buf = read_sha1_file(prev, &type, &size);\n \n-\t\tstrbuf_grow(buf, size + 1);\n-\t\tif (buf->len && prev_buf && size)\n-\t\t\tstrbuf_insert(buf, 0, \"\\n\", 1);\n+\t\tstrbuf_grow(&(msg->buf), size + 1);\n+\t\tif (msg->buf.len && prev_buf && size)\n+\t\t\tstrbuf_insert(&(msg->buf), 0, \"\\n\", 1);\n \t\tif (prev_buf && size)\n-\t\t\tstrbuf_insert(buf, 0, prev_buf, size);\n+\t\t\tstrbuf_insert(&(msg->buf), 0, prev_buf, size);\n \t\tfree(prev_buf);\n \t}\n \n-\tif (!buf->len) {\n+\tif (!msg->buf.len) {\n \t\tfprintf(stderr, \"Removing note for object %s\\n\",\n \t\t\tsha1_to_hex(object));\n \t\thashclr(result);\n \t} else {\n-\t\tif (write_sha1_file(buf->buf, buf->len, blob_type, result)) {\n+\t\tif (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {\n \t\t\terror(\"unable to write note object\");\n \t\t\tif (path)\n \t\t\t\terror(\"The note contents has been left in %s\",\n@@ -160,20 +162,39 @@ static void create_note(const unsigned char *object,\n \t}\n }\n \n-struct msg_arg {\n-\tint given;\n-\tstruct strbuf buf;\n-};\n-\n static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct msg_arg *msg = opt->value;\n \n \tif (!arg)\n \t\treturn -1;\n+\n+\tstrbuf_grow(&(msg->buf), strlen(arg) + 2);\n \tif (msg->buf.len)\n-\t\tstrbuf_addstr(&(msg->buf), \"\\n\\n\");\n+\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n \tstrbuf_addstr(&(msg->buf), arg);\n+\tstripspace(&(msg->buf), 0);\n+\n+\tmsg->given = 1;\n+\treturn 0;\n+}\n+\n+static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct msg_arg *msg = opt->value;\n+\n+\tif (!arg)\n+\t\treturn -1;\n+\n+\tif (msg->buf.len)\n+\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n+\tif (!strcmp(arg, \"-\")) {\n+\t\tif (strbuf_read(&(msg->buf), 0, 1024) < 0)\n+\t\t\tdie_errno(\"cannot read '%s'\", arg);\n+\t} else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)\n+\t\tdie_errno(\"could not open or read '%s'\", arg);\n+\tstripspace(&(msg->buf), 0);\n+\n \tmsg->given = 1;\n \treturn 0;\n }\n@@ -220,7 +241,6 @@ int commit_notes(struct notes_tree *t, const char *msg)\n \n int cmd_notes(int argc, const char **argv, const char *prefix)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n \tstruct notes_tree *t;\n \tunsigned char object[20], new_note[20];\n \tconst unsigned char *note;\n@@ -230,13 +250,13 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tint list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,\n \t    prune = 0, force = 0;\n \tint given_object;\n-\tconst char *msgfile = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes options\"),\n-\t\tOPT_CALLBACK('m', \"message\", &msg, \"msg\",\n+\t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n \t\t\t     \"note contents as a string\", parse_msg_arg),\n-\t\tOPT_FILENAME('F', \"file\", &msgfile, \"note contents in a file\"),\n+\t\tOPT_CALLBACK('F', \"file\", &msg, \"FILE\",\n+\t\t\t     \"note contents in a file\", parse_file_arg),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n \t\tOPT_END()\n \t};\n@@ -265,21 +285,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tif (list + add + append + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n-\tif ((msg.given || msgfile) && !(add || append || edit)) {\n+\tif (msg.given && !(add || append || edit)) {\n \t\terror(\"cannot use -m/-F options with %s subcommand.\", argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n \n-\tif ((msg.given || msgfile) && edit) {\n+\tif (msg.given && edit) {\n \t\tfprintf(stderr, \"The -m and -F options has been deprecated for\"\n \t\t\t\" the 'edit' subcommand.\\n\"\n \t\t\t\"Please use 'git notes add -f -m/-F' instead.\\n\");\n \t}\n \n-\tif (msg.given && msgfile) {\n-\t\terror(\"mixing -m and -F options is not allowed.\");\n-\t\tusage_with_options(git_notes_usage, options);\n-\t}\n \n \tif (force && !add) {\n \t\terror(\"cannot use -f option with %s subcommand.\", argv[0]);\n@@ -341,24 +357,16 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n-\tif (remove)\n-\t\tstrbuf_reset(&buf);\n-\telse if (msg.given)\n-\t\tstrbuf_addbuf(&buf, &(msg.buf));\n-\telse if (msgfile) {\n-\t\tif (!strcmp(msgfile, \"-\")) {\n-\t\t\tif (strbuf_read(&buf, 0, 1024) < 0)\n-\t\t\t\tdie_errno(\"cannot read '%s'\", msgfile);\n-\t\t} else if (strbuf_read_file(&buf, msgfile, 1024) < 0)\n-\t\t\tdie_errno(\"could not open or read '%s'\", msgfile);\n+\tif (remove) {\n+\t\tmsg.given = 1;\n+\t\tstrbuf_reset(&(msg.buf));\n \t}\n \n \tif (prune) {\n \t\thashclr(new_note);\n \t\tprune_notes(t);\n \t} else {\n-\t\tcreate_note(object, &buf, msg.given || msgfile || remove,\n-\t\t\t    append, note, new_note);\n+\t\tcreate_note(object, &msg, append, note, new_note);\n \t\tif (is_null_sha1(new_note))\n \t\t\tremove_note(t, object);\n \t\telse\n@@ -369,6 +377,6 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tcommit_notes(t, logmsg);\n \n \tfree_notes(t);\n-\tstrbuf_release(&buf);\n+\tstrbuf_release(&(msg.buf));\n \treturn 0;\n }\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 290ed63..07090e3 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -284,8 +284,29 @@ test_expect_success 'verify non-creation of note with -m \"\"' '\n \t! git notes show\n '\n \n+cat > expect-combine_m_and_F << EOF\n+foo\n+\n+xyzzy\n+\n+bar\n+\n+zyxxy\n+\n+baz\n+EOF\n+\n+test_expect_success 'create note with combination of -m and -F' '\n+\techo \"xyzzy\" > note_a &&\n+\techo \"zyxxy\" > note_b &&\n+\tgit notes add -m \"foo\" -F note_a -m \"bar\" -F note_b -m \"baz\" &&\n+\tgit notes show > output &&\n+\ttest_cmp expect-combine_m_and_F output\n+'\n+\n test_expect_success 'remove note with \"git notes remove\" (setup)' '\n-\tgit notes remove HEAD^\n+\tgit notes remove HEAD^ &&\n+\tgit notes remove\n '\n \n cat > expect-rm-remove << EOF\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134485","messageId":"1266096518-2104-29-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 28/30] builtin-notes: Add -c/-C options for reusing notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:36Z","receivedAt":"2010-02-13T21:28:36Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Inspired by the -c/-C options to \"git commit\", we teach these options to\n\"git notes add/append\" to allow reuse of note objects.\n\nWith this patch in place, it is now easy to copy or move notes between\nobjects. For example, to copy object A's notes to object B:\n\tgit notes add [-f] -C $(git notes list A) B\nTo move instead of copying, you simply remove the notes from the source\nobject afterwards, e.g.:\n\tgit notes remove A\n\nThe patch includes tests verifying correct behaviour of the new functionality.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   12 ++++-\n builtin-notes.c             |   63 +++++++++++++++++++----\n t/t3301-notes.sh            |  116 +++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 179 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 53c5d90..15de4b3 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -9,8 +9,8 @@ SYNOPSIS\n --------\n [verse]\n 'git notes' [list [<object>]]\n-'git notes' add [-f] [-F <file> | -m <msg>] [<object>]\n-'git notes' append [-F <file> | -m <msg>] [<object>]\n+'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n+'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [<object>]\n 'git notes' show [<object>]\n 'git notes' remove [<object>]\n@@ -84,6 +84,14 @@ OPTIONS\n \tTake the note message from the given file.  Use '-' to\n \tread the note message from the standard input.\n \n+-C <object>::\n+--reuse-message=<object>::\n+\tReuse the note message from the given note object.\n+\n+-c <object>::\n+--reedit-message=<object>::\n+\tLike '-C', but with '-c' the editor is invoked, so that\n+\tthe user can further edit the note message.\n \n Author\n ------\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 190c46c..98de115 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -19,8 +19,8 @@\n \n static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n-\t\"git notes add [-f] [-m <msg> | -F <file>] [<object>]\",\n-\t\"git notes append [-m <msg> | -F <file>] [<object>]\",\n+\t\"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n \t\"git notes edit [<object>]\",\n \t\"git notes show [<object>]\",\n \t\"git notes remove [<object>]\",\n@@ -36,6 +36,7 @@ static const char note_template[] =\n \n struct msg_arg {\n \tint given;\n+\tint use_editor;\n \tstruct strbuf buf;\n };\n \n@@ -104,7 +105,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,\n {\n \tchar *path = NULL;\n \n-\tif (!msg->given) {\n+\tif (msg->use_editor || !msg->given) {\n \t\tint fd;\n \n \t\t/* write the template message before editing: */\n@@ -113,13 +114,16 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,\n \t\tif (fd < 0)\n \t\t\tdie_errno(\"could not create file '%s'\", path);\n \n-\t\tif (prev && !append_only)\n+\t\tif (msg->given)\n+\t\t\twrite_or_die(fd, msg->buf.buf, msg->buf.len);\n+\t\telse if (prev && !append_only)\n \t\t\twrite_note_data(fd, prev);\n \t\twrite_or_die(fd, note_template, strlen(note_template));\n \n \t\twrite_commented_object(fd, object);\n \n \t\tclose(fd);\n+\t\tstrbuf_reset(&(msg->buf));\n \n \t\tif (launch_editor(path, &(msg->buf), NULL)) {\n \t\t\tdie(\"Please supply the note contents using either -m\" \\\n@@ -199,6 +203,40 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n \treturn 0;\n }\n \n+static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct msg_arg *msg = opt->value;\n+\tchar *buf;\n+\tunsigned char object[20];\n+\tenum object_type type;\n+\tunsigned long len;\n+\n+\tif (!arg)\n+\t\treturn -1;\n+\n+\tif (msg->buf.len)\n+\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n+\n+\tif (get_sha1(arg, object))\n+\t\tdie(\"Failed to resolve '%s' as a valid ref.\", arg);\n+\tif (!(buf = read_sha1_file(object, &type, &len)) || !len) {\n+\t\tfree(buf);\n+\t\tdie(\"Failed to read object '%s'.\", arg);;\n+\t}\n+\tstrbuf_add(&(msg->buf), buf, len);\n+\tfree(buf);\n+\n+\tmsg->given = 1;\n+\treturn 0;\n+}\n+\n+static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)\n+{\n+\tstruct msg_arg *msg = opt->value;\n+\tmsg->use_editor = 1;\n+\treturn parse_reuse_arg(opt, arg, unset);\n+}\n+\n int commit_notes(struct notes_tree *t, const char *msg)\n {\n \tstruct commit_list *parent;\n@@ -250,13 +288,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tint list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,\n \t    prune = 0, force = 0;\n \tint given_object;\n-\tstruct msg_arg msg = { 0, STRBUF_INIT };\n+\tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes options\"),\n \t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n \t\t\t     \"note contents as a string\", parse_msg_arg),\n \t\tOPT_CALLBACK('F', \"file\", &msg, \"FILE\",\n \t\t\t     \"note contents in a file\", parse_file_arg),\n+\t\tOPT_CALLBACK('c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t   \"reuse and edit specified note object\", parse_reedit_arg),\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_END()\n \t};\n@@ -286,17 +328,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_notes_usage, options);\n \n \tif (msg.given && !(add || append || edit)) {\n-\t\terror(\"cannot use -m/-F options with %s subcommand.\", argv[0]);\n+\t\terror(\"cannot use -m/-F/-c/-C options with %s subcommand.\",\n+\t\t      argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n \n \tif (msg.given && edit) {\n-\t\tfprintf(stderr, \"The -m and -F options has been deprecated for\"\n-\t\t\t\" the 'edit' subcommand.\\n\"\n-\t\t\t\"Please use 'git notes add -f -m/-F' instead.\\n\");\n+\t\tfprintf(stderr, \"The -m/-F/-c/-C options have been deprecated \"\n+\t\t\t\"for the 'edit' subcommand.\\n\"\n+\t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n \t}\n \n-\n \tif (force && !add) {\n \t\terror(\"cannot use -f option with %s subcommand.\", argv[0]);\n \t\tusage_with_options(git_notes_usage, options);\n@@ -359,6 +401,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tif (remove) {\n \t\tmsg.given = 1;\n+\t\tmsg.use_editor = 0;\n \t\tstrbuf_reset(&(msg.buf));\n \t}\n \ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 07090e3..6447e5f 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -465,4 +465,120 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '\n \ttest_cmp expect actual\n '\n \n+cat > expect << EOF\n+commit 2ede89468182a62d0bde2583c736089bcf7d7e92\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:19:13 2005 -0700\n+\n+    7th\n+\n+Notes:\n+    other note\n+EOF\n+\n+test_expect_success 'create note from other note with \"git notes add -C\"' '\n+\t: > a7 &&\n+\tgit add a7 &&\n+\ttest_tick &&\n+\tgit commit -m 7th &&\n+\tgit notes add -C $(git notes list HEAD^) &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual &&\n+\ttest \"$(git notes list HEAD)\" = \"$(git notes list HEAD^)\"\n+'\n+\n+test_expect_success 'create note from non-existing note with \"git notes add -C\" fails' '\n+\t: > a8 &&\n+\tgit add a8 &&\n+\ttest_tick &&\n+\tgit commit -m 8th &&\n+\ttest_must_fail git notes add -C deadbeef &&\n+\ttest_must_fail git notes list HEAD\n+'\n+\n+cat > expect << EOF\n+commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:21:13 2005 -0700\n+\n+    9th\n+\n+Notes:\n+    yet another note\n+EOF\n+\n+test_expect_success 'create note from other note with \"git notes add -c\"' '\n+\t: > a9 &&\n+\tgit add a9 &&\n+\ttest_tick &&\n+\tgit commit -m 9th &&\n+\tMSG=\"yet another note\" git notes add -c $(git notes list HEAD^^) &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'create note from non-existing note with \"git notes add -c\" fails' '\n+\t: > a10 &&\n+\tgit add a10 &&\n+\ttest_tick &&\n+\tgit commit -m 10th &&\n+\ttest_must_fail MSG=\"yet another note\" git notes add -c deadbeef &&\n+\ttest_must_fail git notes list HEAD\n+'\n+\n+cat > expect << EOF\n+commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:21:13 2005 -0700\n+\n+    9th\n+\n+Notes:\n+    yet another note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'append to note from other note with \"git notes append -C\"' '\n+\tgit notes append -C $(git notes list HEAD^) HEAD^ &&\n+\tgit log -1 HEAD^ > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat > expect << EOF\n+commit ffed603236bfa3891c49644257a83598afe8ae5a\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:22:13 2005 -0700\n+\n+    10th\n+\n+Notes:\n+    other note\n+EOF\n+\n+test_expect_success 'create note from other note with \"git notes append -c\"' '\n+\tMSG=\"other note\" git notes append -c $(git notes list HEAD^) &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+cat > expect << EOF\n+commit ffed603236bfa3891c49644257a83598afe8ae5a\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:22:13 2005 -0700\n+\n+    10th\n+\n+Notes:\n+    other note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'append to note from other note with \"git notes append -c\"' '\n+\tMSG=\"yet another note\" git notes append -c $(git notes list HEAD) &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134483","messageId":"1266096518-2104-30-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 29/30] builtin-notes: Misc. refactoring of argc and exit value handling","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:37Z","receivedAt":"2010-02-13T21:28:37Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This is in preparation of future patches that add additional subcommands.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n builtin-notes.c |   61 ++++++++++++++++++++++++++++++++----------------------\n 1 files changed, 36 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 98de115..bbf98a9 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -287,7 +287,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \tint list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,\n \t    prune = 0, force = 0;\n-\tint given_object;\n+\tint given_object = 0, i = 1, retval = 0;\n \tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n \tstruct option options[] = {\n \t\tOPT_GROUP(\"Notes options\"),\n@@ -321,8 +321,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tremove = 1;\n \telse if (argc && !strcmp(argv[0], \"prune\"))\n \t\tprune = 1;\n-\telse if (!argc)\n+\telse if (!argc) {\n \t\tlist = 1; /* Default to 'list' if no other subcommand given */\n+\t\ti = 0;\n+\t}\n \n \tif (list + add + append + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n@@ -344,9 +346,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-\tgiven_object = argc == 2;\n-\tobject_ref = given_object ? argv[1] : \"HEAD\";\n-\tif (argc > 2 || (prune && argc > 1)) {\n+\tgiven_object = argc > i;\n+\tobject_ref = given_object ? argv[i++] : \"HEAD\";\n+\n+\tif (argc > i || (prune && given_object)) {\n \t\terror(\"too many parameters\");\n \t\tusage_with_options(git_notes_usage, options);\n \t}\n@@ -369,34 +372,38 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tif (given_object) {\n \t\t\tif (note) {\n \t\t\t\tputs(sha1_to_hex(note));\n-\t\t\t\treturn 0;\n+\t\t\t\tgoto end;\n \t\t\t}\n-\t\t} else\n-\t\t\treturn for_each_note(t, 0, list_each_note, NULL);\n+\t\t} else {\n+\t\t\tretval = for_each_note(t, 0, list_each_note, NULL);\n+\t\t\tgoto end;\n+\t\t}\n \t}\n \n \t/* show command */\n \n \tif ((list || show) && !note) {\n \t\terror(\"No note found for object %s.\", sha1_to_hex(object));\n-\t\treturn 1;\n+\t\tretval = 1;\n+\t\tgoto end;\n \t} else if (show) {\n \t\tconst char *show_args[3] = {\"show\", sha1_to_hex(note), NULL};\n-\t\treturn execv_git_cmd(show_args);\n+\t\tretval = execv_git_cmd(show_args);\n+\t\tgoto end;\n \t}\n \n \t/* add/append/edit/remove/prune command */\n \n \tif (add && note) {\n-\t\tif (force)\n-\t\t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n-\t\t\t\tsha1_to_hex(object));\n-\t\telse {\n-\t\t\terror(\"Cannot add notes. Found existing notes for object %s. \"\n-\t\t\t      \"Use '-f' to overwrite existing notes\",\n+\t\tif (!force) {\n+\t\t\terror(\"Cannot add notes. Found existing notes for object\"\n+\t\t\t      \" %s. Use '-f' to overwrite existing notes\",\n \t\t\t      sha1_to_hex(object));\n-\t\t\treturn 1;\n+\t\t\tretval = 1;\n+\t\t\tgoto end;\n \t\t}\n+\t\tfprintf(stderr, \"Overwriting existing notes for object %s\\n\",\n+\t\t\tsha1_to_hex(object));\n \t}\n \n \tif (remove) {\n@@ -408,18 +415,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \tif (prune) {\n \t\thashclr(new_note);\n \t\tprune_notes(t);\n-\t} else {\n+\t\tgoto commit;\n+\t} else\n \t\tcreate_note(object, &msg, append, note, new_note);\n-\t\tif (is_null_sha1(new_note))\n-\t\t\tremove_note(t, object);\n-\t\telse\n-\t\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n-\t}\n-\tsnprintf(logmsg, sizeof(logmsg), \"Note %s by 'git notes %s'\",\n+\n+\tif (is_null_sha1(new_note))\n+\t\tremove_note(t, object);\n+\telse\n+\t\tadd_note(t, object, new_note, combine_notes_overwrite);\n+\n+commit:\n+\tsnprintf(logmsg, sizeof(logmsg), \"Notes %s by 'git notes %s'\",\n \t\t is_null_sha1(new_note) ? \"removed\" : \"added\", argv[0]);\n \tcommit_notes(t, logmsg);\n \n+end:\n \tfree_notes(t);\n \tstrbuf_release(&(msg.buf));\n-\treturn 0;\n+\treturn retval;\n }\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134488","messageId":"1266096518-2104-31-git-send-email-johan@herland.net","threadId":"22647","inReplyTo":"1266096518-2104-1-git-send-email-johan@herland.net","subject":"[PATCHv13 30/30] builtin-notes: Add \"copy\" subcommand for copying notes between objects","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-13T21:28:38Z","receivedAt":"2010-02-13T21:28:38Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This is useful for keeping notes to objects that are being rewritten by e.g.\n'git commit --amend', 'git rebase', or 'git cherry-pick'.\n\n\"git notes copy <from> <to>\" is in practice equivalent to\n\"git notes add -C $(git notes list <from>) <to>\", although it is somewhat\nmore convenient for regular users.\n\n\"git notes copy\" takes the same -f option as \"git add\", to overwrite existing\nnotes at the target (instead of aborting with an error message).\n\nIf the <from>-object has no notes, \"git notes copy\" will abort with an error\nmessage.\n\nThe patch includes tests verifying correct behaviour of the new subcommand.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |    8 +++++\n builtin-notes.c             |   39 +++++++++++++++++++++-----\n t/t3301-notes.sh            |   63 +++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 102 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 15de4b3..14f73b9 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -10,6 +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' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]\n 'git notes' edit [<object>]\n 'git notes' show [<object>]\n@@ -48,6 +49,13 @@ add::\n \tobject already has notes, abort. (use `-f` to overwrite an\n \texisting note).\n \n+copy::\n+\tCopy the notes for the first object onto the second object.\n+\tAbort if the second object already has notes, or if the first\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 append::\n \tAppend to the notes of an existing object (defaults to HEAD).\n \tCreates a new notes object if needed.\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex bbf98a9..123ecad 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -20,6 +20,7 @@\n static const char * const git_notes_usage[] = {\n \t\"git notes [list [<object>]]\",\n \t\"git notes add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n+\t\"git notes copy [-f] <from-object> <to-object>\",\n \t\"git notes append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]\",\n \t\"git notes edit [<object>]\",\n \t\"git notes show [<object>]\",\n@@ -280,13 +281,13 @@ int commit_notes(struct notes_tree *t, const char *msg)\n int cmd_notes(int argc, const char **argv, const char *prefix)\n {\n \tstruct notes_tree *t;\n-\tunsigned char object[20], new_note[20];\n+\tunsigned char object[20], from_obj[20], new_note[20];\n \tconst unsigned char *note;\n \tconst char *object_ref;\n \tchar logmsg[100];\n \n-\tint list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0,\n-\t    prune = 0, force = 0;\n+\tint list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,\n+\t    remove = 0, prune = 0, force = 0;\n \tint given_object = 0, i = 1, retval = 0;\n \tstruct msg_arg msg = { 0, 0, STRBUF_INIT };\n \tstruct option options[] = {\n@@ -311,6 +312,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\tlist = 1;\n \telse if (argc && !strcmp(argv[0], \"add\"))\n \t\tadd = 1;\n+\telse if (argc && !strcmp(argv[0], \"copy\"))\n+\t\tcopy = 1;\n \telse if (argc && !strcmp(argv[0], \"append\"))\n \t\tappend = 1;\n \telse if (argc && !strcmp(argv[0], \"edit\"))\n@@ -326,7 +329,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\ti = 0;\n \t}\n \n-\tif (list + add + append + edit + show + remove + prune != 1)\n+\tif (list + add + copy + append + edit + show + remove + prune != 1)\n \t\tusage_with_options(git_notes_usage, options);\n \n \tif (msg.given && !(add || append || edit)) {\n@@ -341,11 +344,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\t\t\"Please use 'git notes add -f -m/-F/-c/-C' instead.\\n\");\n \t}\n \n-\tif (force && !add) {\n+\tif (force && !(add || copy)) {\n \t\terror(\"cannot use -f option 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 (argc < 3) {\n+\t\t\terror(\"too few parameters\");\n+\t\t\tusage_with_options(git_notes_usage, options);\n+\t\t}\n+\t\tfrom_ref = argv[i++];\n+\t\tif (get_sha1(from_ref, from_obj))\n+\t\t\tdie(\"Failed to resolve '%s' as a valid ref.\", from_ref);\n+\t}\n+\n \tgiven_object = argc > i;\n \tobject_ref = given_object ? argv[i++] : \"HEAD\";\n \n@@ -394,11 +408,11 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \n \t/* add/append/edit/remove/prune command */\n \n-\tif (add && note) {\n+\tif ((add || copy) && note) {\n \t\tif (!force) {\n-\t\t\terror(\"Cannot add notes. Found existing notes for object\"\n+\t\t\terror(\"Cannot %s notes. Found existing notes for object\"\n \t\t\t      \" %s. Use '-f' to overwrite existing notes\",\n-\t\t\t      sha1_to_hex(object));\n+\t\t\t      argv[0], sha1_to_hex(object));\n \t\t\tretval = 1;\n \t\t\tgoto end;\n \t\t}\n@@ -416,6 +430,15 @@ int cmd_notes(int argc, const char **argv, const char *prefix)\n \t\thashclr(new_note);\n \t\tprune_notes(t);\n \t\tgoto commit;\n+\t} else if (copy) {\n+\t\tconst unsigned char *from_note = get_note(t, from_obj);\n+\t\tif (!from_note) {\n+\t\t\terror(\"Missing notes on source object %s. Cannot copy.\",\n+\t\t\t      sha1_to_hex(from_obj));\n+\t\t\tretval = 1;\n+\t\t\tgoto end;\n+\t\t}\n+\t\thashcpy(new_note, from_note);\n \t} else\n \t\tcreate_note(object, &msg, append, note, new_note);\n \ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 6447e5f..90178f9 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -581,4 +581,67 @@ test_expect_success 'append to note from other note with \"git notes append -c\"'\n \ttest_cmp expect actual\n '\n \n+cat > expect << EOF\n+commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:23:13 2005 -0700\n+\n+    11th\n+\n+Notes:\n+    other note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'copy note with \"git notes copy\"' '\n+\t: > a11 &&\n+\tgit add a11 &&\n+\ttest_tick &&\n+\tgit commit -m 11th &&\n+\tgit notes copy HEAD^ HEAD &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual &&\n+\ttest \"$(git notes list HEAD)\" = \"$(git notes list HEAD^)\"\n+'\n+\n+test_expect_success 'prevent overwrite with \"git notes copy\"' '\n+\ttest_must_fail git notes copy HEAD~2 HEAD &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual &&\n+\ttest \"$(git notes list HEAD)\" = \"$(git notes list HEAD^)\"\n+'\n+\n+cat > expect << EOF\n+commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:23:13 2005 -0700\n+\n+    11th\n+\n+Notes:\n+    yet another note\n+$whitespace\n+    yet another note\n+EOF\n+\n+test_expect_success 'allow overwrite with \"git notes copy -f\"' '\n+\tgit notes copy -f HEAD~2 HEAD &&\n+\tgit log -1 > actual &&\n+\ttest_cmp expect actual &&\n+\ttest \"$(git notes list HEAD)\" = \"$(git notes list HEAD~2)\"\n+'\n+\n+test_expect_success 'cannot copy note from object without notes' '\n+\t: > a12 &&\n+\tgit add a12 &&\n+\ttest_tick &&\n+\tgit commit -m 12th &&\n+\t: > a13 &&\n+\tgit add a13 &&\n+\ttest_tick &&\n+\tgit commit -m 13th &&\n+\ttest_must_fail git notes copy HEAD^ HEAD\n+'\n+\n test_done\n-- \n1.7.0.rc1.141.gd3fd\n"},{"id":"134634","messageId":"4B792347.8070208@gmail.com","threadId":"22647","inReplyTo":"1266096518-2104-29-git-send-email-johan@herland.net","subject":"Re: [PATCHv13 28/30] builtin-notes: Add -c/-C options for reusing notes","fromName":"Stephen Boyd","fromEmail":"bebarino@gmail.com","sentAt":"2010-02-15T10:34:47Z","receivedAt":"2010-02-15T10:34:47Z","isPatch":false,"sender":{"key":"bebarino@gmail.com","avatar":"https://avatars.githubusercontent.com/u/38832?v=4"},"body":"On 02/13/2010 01:28 PM, Johan Herland wrote:\n> @@ -199,6 +203,40 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n>  \treturn 0;\n>  }\n>  \n> +static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n> +{\n> +\tstruct msg_arg *msg = opt->value;\n> +\tchar *buf;\n> +\tunsigned char object[20];\n> +\tenum object_type type;\n> +\tunsigned long len;\n> +\n> +\tif (!arg)\n> +\t\treturn -1;\n\nThis is impossible unless you're using the PARSE_OPT_OPTARG flag or\nallowing negation (i.e. --no-reuse-mesage). You should probably make the\ntwo callback options PARSE_OPT_NONEG and then drop this if statement.\nSame applies to some of the other callbacks not introduced in this patch.\n\n> +\n> +\tif (msg->buf.len)\n> +\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n> +\n\nUse strbuf_addch()? I saw this in a couple other patches too.\n"},{"id":"134687","messageId":"201002160247.49719.johan@herland.net","threadId":"22647","inReplyTo":"4B792347.8070208@gmail.com","subject":"Re: [PATCHv13 28/30] builtin-notes: Add -c/-C options for reusing notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-16T01:47:49Z","receivedAt":"2010-02-16T01:47:49Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Monday 15 February 2010, Stephen Boyd wrote:\n> On 02/13/2010 01:28 PM, Johan Herland wrote:\n> > @@ -199,6 +203,40 @@ static int parse_file_arg(const struct option\n> > *opt, const char *arg, int unset)\n> > \n> >  \treturn 0;\n> >  \n> >  }\n> > \n> > +static int parse_reuse_arg(const struct option *opt, const char *arg,\n> > int unset) +{\n> > +\tstruct msg_arg *msg = opt->value;\n> > +\tchar *buf;\n> > +\tunsigned char object[20];\n> > +\tenum object_type type;\n> > +\tunsigned long len;\n> > +\n> > +\tif (!arg)\n> > +\t\treturn -1;\n> \n> This is impossible unless you're using the PARSE_OPT_OPTARG flag or\n> allowing negation (i.e. --no-reuse-mesage). You should probably make the\n> two callback options PARSE_OPT_NONEG and then drop this if statement.\n> Same applies to some of the other callbacks not introduced in this patch.\n\nThanks. Fixed locally. Will be part of the next iteration.\n\n> > +\n> > +\tif (msg->buf.len)\n> > +\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n> > +\n> \n> Use strbuf_addch()? I saw this in a couple other patches too.\n\nOf course. Thanks for noticing. Fixed locally. Will be part of the next \niteration.\n\n\nThanks for the review! :)\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"135643","messageId":"201002250148.11487.johan@herland.net","threadId":"22647","inReplyTo":"201002160247.49719.johan@herland.net","subject":"[PATCH] builtin-notes: Minor (mostly parse_options-related) fixes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2010-02-25T00:48:11Z","receivedAt":"2010-02-25T00:48:11Z","isPatch":true,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Use PARSE_OPT_NONEG to disallow --no-<option> for --message, --file,\n--reedit-message and --reuse-message. --no-<option> does not make sense\nfor these options, and specifying PARSE_OPT_NONEG simplifies the code in\nthe option-handling callbacks.\n\nAlso, use strbuf_addch(... '\\n') instead of strbuf_addstr(... \"\\n\") in\ncouple of places.\n\nFinally, improve the short-help by dividing the options into two OPT_GROUPs.\n\nSuggested-by: Stephen Boyd <bebarino@gmail.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n\nOn Tuesday 16 February 2010, Johan Herland wrote:\n> On Monday 15 February 2010, Stephen Boyd wrote:\n> > On 02/13/2010 01:28 PM, Johan Herland wrote:\n> > > @@ -199,6 +203,40 @@ static int parse_file_arg(const struct option\n\n[...]\n\n> > > +\tif (!arg)\n> > > +\t\treturn -1;\n> > \n> > This is impossible unless you're using the PARSE_OPT_OPTARG flag or\n> > allowing negation (i.e. --no-reuse-mesage). You should probably make\n> > the two callback options PARSE_OPT_NONEG and then drop this if\n> > statement. Same applies to some of the other callbacks not introduced\n> > in this patch.\n> \n> Thanks. Fixed locally. Will be part of the next iteration.\n> \n> > > +\n> > > +\tif (msg->buf.len)\n> > > +\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n> > > +\n> > \n> > Use strbuf_addch()? I saw this in a couple other patches too.\n> \n> Of course. Thanks for noticing. Fixed locally. Will be part of the next\n> iteration.\n\nI never got around to submitting the next jh/notes iteration before it was\nmerged to 'next', so here are Stephen's suggestions as a separate patch on\ntop of 'next'. AFAICS it does not conflict with tr/notes-display in 'pu'.\n\n\nHave fun! :)\n\n...Johan\n\n\n builtin-notes.c |   38 +++++++++++++++++---------------------\n 1 files changed, 17 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin-notes.c b/builtin-notes.c\nindex 123ecad..feb710a 100644\n--- a/builtin-notes.c\n+++ b/builtin-notes.c\n@@ -171,12 +171,9 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct msg_arg *msg = opt->value;\n \n-\tif (!arg)\n-\t\treturn -1;\n-\n \tstrbuf_grow(&(msg->buf), strlen(arg) + 2);\n \tif (msg->buf.len)\n-\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n+\t\tstrbuf_addch(&(msg->buf), '\\n');\n \tstrbuf_addstr(&(msg->buf), arg);\n \tstripspace(&(msg->buf), 0);\n \n@@ -188,11 +185,8 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset)\n {\n \tstruct msg_arg *msg = opt->value;\n \n-\tif (!arg)\n-\t\treturn -1;\n-\n \tif (msg->buf.len)\n-\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n+\t\tstrbuf_addch(&(msg->buf), '\\n');\n \tif (!strcmp(arg, \"-\")) {\n \t\tif (strbuf_read(&(msg->buf), 0, 1024) < 0)\n \t\t\tdie_errno(\"cannot read '%s'\", arg);\n@@ -212,11 +206,8 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)\n \tenum object_type type;\n \tunsigned long len;\n \n-\tif (!arg)\n-\t\treturn -1;\n-\n \tif (msg->buf.len)\n-\t\tstrbuf_addstr(&(msg->buf), \"\\n\");\n+\t\tstrbuf_addch(&(msg->buf), '\\n');\n \n \tif (get_sha1(arg, object))\n \t\tdie(\"Failed to resolve '%s' as a valid ref.\", arg);\n@@ -291,15 +282,20 @@ 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 \tstruct option options[] = {\n-\t\tOPT_GROUP(\"Notes options\"),\n-\t\tOPT_CALLBACK('m', \"message\", &msg, \"MSG\",\n-\t\t\t     \"note contents as a string\", parse_msg_arg),\n-\t\tOPT_CALLBACK('F', \"file\", &msg, \"FILE\",\n-\t\t\t     \"note contents in a file\", parse_file_arg),\n-\t\tOPT_CALLBACK('c', \"reedit-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse and edit specified note object\", parse_reedit_arg),\n-\t\tOPT_CALLBACK('C', \"reuse-message\", &msg, \"OBJECT\",\n-\t\t\t   \"reuse specified note object\", parse_reuse_arg),\n+\t\tOPT_GROUP(\"Notes contents options\"),\n+\t\t{ OPTION_CALLBACK, 'm', \"message\", &msg, \"MSG\",\n+\t\t\t\"note contents as a string\", PARSE_OPT_NONEG,\n+\t\t\tparse_msg_arg},\n+\t\t{ OPTION_CALLBACK, 'F', \"file\", &msg, \"FILE\",\n+\t\t\t\"note contents in a file\", PARSE_OPT_NONEG,\n+\t\t\tparse_file_arg},\n+\t\t{ OPTION_CALLBACK, 'c', \"reedit-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse and edit specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reedit_arg},\n+\t\t{ OPTION_CALLBACK, 'C', \"reuse-message\", &msg, \"OBJECT\",\n+\t\t\t\"reuse specified note object\", PARSE_OPT_NONEG,\n+\t\t\tparse_reuse_arg},\n+\t\tOPT_GROUP(\"Other options\"),\n \t\tOPT_BOOLEAN('f', \"force\", &force, \"replace existing notes\"),\n \t\tOPT_END()\n \t};\n-- \n1.7.0.rc1.141.gd3fd\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"135648","messageId":"4B85E96E.1080808@gmail.com","threadId":"22647","inReplyTo":"201002250148.11487.johan@herland.net","subject":"Re: [PATCH] builtin-notes: Minor (mostly parse_options-related) fixes","fromName":"Stephen Boyd","fromEmail":"bebarino@gmail.com","sentAt":"2010-02-25T03:07:26Z","receivedAt":"2010-02-25T03:07:26Z","isPatch":true,"sender":{"key":"bebarino@gmail.com","avatar":"https://avatars.githubusercontent.com/u/38832?v=4"},"body":"On 02/24/2010 04:48 PM, Johan Herland wrote:\n> Use PARSE_OPT_NONEG to disallow --no-<option> for --message, --file,\n> --reedit-message and --reuse-message. --no-<option> does not make sense\n> for these options, and specifying PARSE_OPT_NONEG simplifies the code in\n> the option-handling callbacks.\n>\n> Also, use strbuf_addch(... '\\n') instead of strbuf_addstr(... \"\\n\") in\n> couple of places.\n>\n> Finally, improve the short-help by dividing the options into two OPT_GROUPs.\n>\n> Suggested-by: Stephen Boyd <bebarino@gmail.com>\n> Signed-off-by: Johan Herland <johan@herland.net>\n\nAcked-by: Stephen Boyd <bebarino@gmail.com>\n\nOn a related note, I spent last night splitting up cmd_notes() into\nsubcommands. This way it's more like builtin-remote.c with respect to\nthe many subcommands and allows you to do something like 'git notes add\n-h' and only see the options for add. I'll send it in a few hours after\nI have time to polish it up (and probably rebase onto this change).\n"}]}