{"thread":{"id":"21682","subject":"[RFC/PATCHv8 00/10] git notes","startedAt":"2009-11-20T01:39:04Z","lastAt":"2010-01-20T10:06:47Z","messageCount":26,"participants":["Johan Herland","Junio C Hamano","Nanako Shiraishi","Johannes Schindelin","Shawn O. Pearce","Alex Riesen","Johannes Sixt"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"127953","messageId":"1258681154-2167-1-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":null,"subject":"[RFC/PATCHv8 00/10] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:04Z","receivedAt":"2009-11-20T01:39:04Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Hi,\n\nHere is the 8th iteration of the git-notes series. Changes in this\niteration are as follows:\n\nChanges to existing patches:\n- Rebased onto current 'next', dropping the early part of this series\n  which has now been merged to 'next'.\n- Patch 8 (was patch 22): Major rewrite of fast-import's notes handling\n  code based on comments from Shawn.\n\nNew patches:\n- Patch 9: Rename t9301 to t9350, to make room for more fast-import tests\n- Patch 10: More fast-import tests\n\nTODO:\n- Builtin-ify git-notes shell script to take advantage of notes API\n- Garbage collect notes whose referenced object is unreachable (gc_notes())\n- Handle note objects that are not blobs, but trees\n\n\nHave fun! :)\n\n...Johan\n\n\nJohan Herland (10):\n  Notes API: get_commit_notes() -> format_note() + remove the commit restriction\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: 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: Allow multiple concurrent notes trees with new struct notes_tree\n  Refactor notes concatenation into a flexible interface for combining notes\n  fast-import: Proper notes tree manipulation using the notes API\n  Rename t9301 to t9350, to make room for more fast-import tests\n  Add more testcases to test fast-import of notes\n\n fast-import.c                                    |  297 +++++++++++-\n notes.c                                          |  336 +++++++++----\n notes.h                                          |  114 ++++-\n pretty.c                                         |    8 +-\n t/t9300-fast-import.sh                           |  156 ++++++-\n t/t9301-fast-import-notes.sh                     |  578 ++++++++++++++++++++++\n t/{t9301-fast-export.sh => t9350-fast-export.sh} |    0\n 7 files changed, 1370 insertions(+), 119 deletions(-)\n create mode 100755 t/t9301-fast-import-notes.sh\n rename t/{t9301-fast-export.sh => t9350-fast-export.sh} (100%)\n"},{"id":"127955","messageId":"1258681154-2167-2-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 01/10] Notes API: get_commit_notes() -> format_note() + remove the commit restriction","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:05Z","receivedAt":"2009-11-20T01:39:05Z","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 50a4672..0f7082f 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 \"refs.h\"\n #include \"utf8.h\"\n@@ -25,10 +24,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@@ -211,7 +210,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@@ -299,7 +298,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@@ -312,23 +311,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\tl->key_sha1[19] = (unsigned char) len;\n@@ -342,12 +341,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@@ -355,9 +354,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@@ -370,7 +369,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@@ -389,7 +388,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 5661cba..771d186 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -775,8 +775,8 @@ static size_t format_commit_item(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@@ -1057,8 +1057,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \t\tstrbuf_addch(sb, '\\n');\n \n \tif (fmt != CMIT_FMT_ONELINE)\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.6.4.304.g1365c.dirty\n"},{"id":"127957","messageId":"1258681154-2167-3-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 02/10] Notes API: init_notes(): Initialize the notes tree from the given notes ref","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:06Z","receivedAt":"2009-11-20T01:39:06Z","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 |   27 ++++++++++++++++-----------\n notes.h |   20 ++++++++++++++++++++\n 2 files changed, 36 insertions(+), 11 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 0f7082f..f2bacbb 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -339,13 +339,25 @@ 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+\tassert(!initialized);\n+\tinitialized = 1;\n+\n+\tif (!notes_ref) {\n+\t\tconst char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);\n+\t\tif (env)\n+\t\t\tnotes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);\n+\t\telse\n+\t\t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n+\t}\n+\n+\tif (flags & NOTES_INIT_EMPTY || !notes_ref ||\n+\t    read_ref(notes_ref, object_sha1) ||\n \t    get_tree_entry(object_sha1, \"\", sha1, &mode))\n \t\treturn;\n \n@@ -378,15 +390,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.6.4.304.g1365c.dirty\n"},{"id":"127958","messageId":"1258681154-2167-4-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 03/10] Notes API: add_note(): Add note objects to the internal notes tree structure","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:07Z","receivedAt":"2009-11-20T01:39:07Z","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 f2bacbb..49a3e86 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -366,6 +366,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.6.4.304.g1365c.dirty\n"},{"id":"127954","messageId":"1258681154-2167-5-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 04/10] Notes API: get_note(): Return the note annotating the given object","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:08Z","receivedAt":"2009-11-20T01:39:08Z","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 |    3 +++\n 2 files changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 49a3e86..2196a5f 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -377,12 +377,13 @@ 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-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@@ -396,7 +397,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@@ -404,7 +405,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 5f22852..21a8930 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+/* Get the note object SHA1 containing the note data for the given object */\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.6.4.304.g1365c.dirty\n"},{"id":"127956","messageId":"1258681154-2167-6-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 05/10] Notes API: for_each_note(): Traverse the entire notes tree with a callback","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:09Z","receivedAt":"2009-11-20T01:39:09Z","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 |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n notes.h |   17 ++++++++++\n 2 files changed, 118 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 2196a5f..9581b98 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -339,6 +339,101 @@ 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 any of them are subtree entries, then\n+\t * there are likely plenty of notes below this level, so we return an\n+\t * incremented fanout immediately. Otherwise, we return an incremented\n+\t * fanout only if all of the entries at this level are int_nodes.\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\t\treturn fanout + 1;\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, each_note_fn fn, void *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(\n+\t\t\t\tCLR_PTR_TYPE(p), n + 1, fanout, fn, cb_data);\n+\t\t\tbreak;\n+\t\tcase PTR_TYPE_SUBTREE:\n+\t\t\t/* unpack subtree and resume traversal */\n+\t\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\t\ttree->a[i] = NULL;\n+\t\t\tload_subtree(l, tree, n);\n+\t\t\tfree(l);\n+\t\t\tgoto redo;\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@@ -386,6 +481,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(each_note_fn fn, void *cb_data)\n+{\n+\tassert(initialized);\n+\treturn for_each_note_helper(&root_node, 0, 0, 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 21a8930..f67bae8 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -28,6 +28,23 @@ void add_note(const unsigned char *object_sha1,\n /* Get the note object SHA1 containing the note data for the given object */\n const unsigned char *get_note(const unsigned char *object_sha1);\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().\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+ * - free_notes()\n+ */\n+typedef int each_note_fn(const unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1, const char *note_tree_path,\n+\t\tvoid *cb_data);\n+int for_each_note(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.6.4.304.g1365c.dirty\n"},{"id":"127959","messageId":"1258681154-2167-7-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 06/10] Notes API: Allow multiple concurrent notes trees with new struct notes_tree","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:10Z","receivedAt":"2009-11-20T01:39:10Z","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 interface with several different notes\ntrees simultaneously.\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, a falback \"default\" notes\ntree (declared in notes.c) is used.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c  |   67 ++++++++++++++++++++++++++++++++++++++-----------------------\n notes.h  |   53 +++++++++++++++++++++++++++++++++++-------------\n pretty.c |    4 +-\n 3 files changed, 82 insertions(+), 42 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 9581b98..a5d9736 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+static struct notes_tree default_tree;\n \n static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \t\tunsigned int n);\n@@ -434,14 +432,15 @@ redo:\n \treturn 0;\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_tree;\n+\tassert(!t->initialized);\n \n \tif (!notes_ref) {\n \t\tconst char *env = getenv(GIT_NOTES_REF_ENVIRONMENT);\n@@ -451,6 +450,10 @@ void init_notes(const char *notes_ref, int flags)\n \t\t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n \t}\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    get_tree_entry(object_sha1, \"\", sha1, &mode))\n@@ -458,44 +461,56 @@ 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_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-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_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(each_note_fn fn, void *cb_data)\n+int for_each_note(struct notes_tree *t, each_note_fn fn, void *cb_data)\n {\n-\tassert(initialized);\n-\treturn for_each_note_helper(&root_node, 0, 0, fn, cb_data);\n+\tif (!t)\n+\t\tt = &default_tree;\n+\tassert(t->initialized);\n+\treturn for_each_note_helper(t->root, 0, 0, fn, cb_data);\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_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@@ -503,10 +518,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_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 f67bae8..ea1235f 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+struct notes_tree {\n+\tstruct int_node *root;\n+\tchar *ref;\n+\tint initialized;\n+};\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,26 +25,32 @@\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-/* Add the given note object to the internal notes tree structure */\n-void add_note(const unsigned char *object_sha1,\n+/* Add the given note object to the given notes_tree structure */\n+void add_note(struct notes_tree *t, const unsigned char *object_sha1,\n \t\tconst unsigned char *note_sha1);\n \n /* Get the note object SHA1 containing the note data for the given object */\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- * 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().\n@@ -43,10 +64,10 @@ 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, const char *note_tree_path,\n \t\tvoid *cb_data);\n-int for_each_note(each_note_fn fn, void *cb_data);\n+int for_each_note(struct notes_tree *t, each_note_fn fn, void *cb_data);\n \n-/* Free (and de-initialize) the internal notes tree structure */\n-void free_notes(void);\n+/* Free (and de-initialize) the give notes_tree structure */\n+void free_notes(struct notes_tree *t);\n \n /* Flags controlling how notes are formatted */\n #define NOTES_SHOW_HEADER 1\n@@ -55,12 +76,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 771d186..eeb2ebd 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -775,7 +775,7 @@ static size_t format_commit_item(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\tformat_note(NULL, 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@@ -1057,7 +1057,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \t\tstrbuf_addch(sb, '\\n');\n \n \tif (fmt != CMIT_FMT_ONELINE)\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.6.4.304.g1365c.dirty\n"},{"id":"127960","messageId":"1258681154-2167-8-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 07/10] Refactor notes concatenation into a flexible interface for combining notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:11Z","receivedAt":"2009-11-20T01:39:11Z","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 |  132 +++++++++++++++++++++++++++++++++++---------------------------\n notes.h |   34 +++++++++++++++-\n 2 files changed, 106 insertions(+), 60 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex a5d9736..19ae492 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -127,55 +127,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 +141,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 +163,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 +190,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 +200,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 /* Free the entire notes data contained in the given tree */\n@@ -331,7 +288,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\tcombine_notes_concatenate);\n \t\t}\n \t}\n \tfree(buf);\n@@ -432,7 +390,59 @@ redo:\n \treturn 0;\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, const 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+\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+int combine_notes_overwrite(unsigned char *cur_sha1, const 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, const 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@@ -450,8 +460,12 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags)\n \t\t\tnotes_ref = GIT_NOTES_DEFAULT_REF;\n \t}\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@@ -465,17 +479,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_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 const unsigned char *get_note(struct notes_tree *t,\n@@ -521,7 +537,7 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,\n \tif (!t)\n \t\tt = &default_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 ea1235f..047a282 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 are guaranteed to be non-NULL and different.\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 struct notes_tree {\n \tstruct int_node *root;\n \tchar *ref;\n+\tcombine_notes_fn *combine_notes;\n \tint initialized;\n };\n \n@@ -36,14 +61,19 @@ 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 /* Add the given note object to the given notes_tree structure */\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 /* Get the note object SHA1 containing the note data for the given object */\n const unsigned char *get_note(struct notes_tree *t,\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"127963","messageId":"1258681154-2167-9-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 08/10] fast-import: Proper notes tree manipulation using the notes API","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:12Z","receivedAt":"2009-11-20T01:39:12Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This patch teaches 'git fast-import' to use the notes API to organize\nthe manipulation of note objects through a fast-import stream.\nNote objects are added both to the notes tree and the \"regular\" tree\nthrough the 'N' command, and when we're about to store the \"regular\" tree\nobject for the current commit, we walk through the notes tree to adjust all\nthe note object locations in the stored tree.\n\nUnloading of a fast-import branch also unloads the corresponding notes\ntree (if any), and the notes tree is (re)loaded when needed.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n\nThis patch is substantially different from the previous iteration.\nUnloading (and reloading) the notes tree along with its corresponding\nbranch was relatively straightforward to fix, but avoiding the\ndestroying and re-adding of all the notes in every commit was much\nharder. After 3-4 attempts at a simpler (but fundamentally broken)\napproach, I finally landed on this. I'm not satisfied with the amount\nof code introduced by this patch, and would be happy if someone found\na better/shorter/more elegant way to solve this problem.\n\n\n...Johan\n\n\n fast-import.c          |  297 +++++++++++++++++++++++++++++++++++++++++++++++-\n t/t9300-fast-import.sh |  156 +++++++++++++++++++++++--\n 2 files changed, 435 insertions(+), 18 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex dd3c99d..be3cbf9 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -156,6 +156,7 @@ Format of STDIN stream:\n #include \"csum-file.h\"\n #include \"quote.h\"\n #include \"exec_cmd.h\"\n+#include \"notes.h\"\n\n #define PACK_ID_BITS 16\n #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)\n@@ -246,6 +247,7 @@ struct branch\n \tstruct tree_entry branch_tree;\n \tuintmax_t last_commit;\n \tunsigned active : 1;\n+\tunsigned has_notes : 1;\n \tunsigned pack_id : PACK_ID_BITS;\n \tunsigned char sha1[20];\n };\n@@ -277,6 +279,33 @@ struct recent_command\n \tchar *buf;\n };\n\n+struct notes_tree_list\n+{\n+\tstruct notes_tree tree;\n+\tstruct notes_tree_list *next;\n+};\n+\n+struct tree_entry_replace\n+{\n+\tstruct tree_entry_replace *next;\n+\tchar *old_path;\n+\tchar *new_path;\n+};\n+\n+struct reconcile_notes_tree_helper_data\n+{\n+\tstruct tree_entry *root;\n+\tstruct tree_entry_replace **replace_list;\n+};\n+\n+struct reconcile_note_and_tree_entry_data\n+{\n+\tconst unsigned char *object_sha1;\n+\tconst unsigned char *note_sha1;\n+\tconst char *note_tree_path;\n+\tstruct tree_entry_replace **replace_list;\n+};\n+\n /* Configured limits on output */\n static unsigned long max_depth = 10;\n static off_t max_packsize = (1LL << 32) - 1;\n@@ -341,6 +370,9 @@ static struct branch *active_branches;\n static struct tag *first_tag;\n static struct tag *last_tag;\n\n+/* Notes data */\n+static struct notes_tree_list *notes_trees;\n+\n /* Input stream parsing */\n static whenspec_type whenspec = WHENSPEC_RAW;\n static struct strbuf command_buf = STRBUF_INIT;\n@@ -694,6 +726,7 @@ static struct branch *new_branch(const char *name)\n \tb->branch_tree.versions[0].mode = S_IFDIR;\n \tb->branch_tree.versions[1].mode = S_IFDIR;\n \tb->active = 0;\n+\tb->has_notes = 0;\n \tb->pack_id = MAX_PACK_ID;\n \tbranch_table[hc] = b;\n \tbranch_count++;\n@@ -1816,6 +1849,221 @@ static void parse_new_blob(void)\n \tstore_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark);\n }\n\n+static struct notes_tree *notes_tree_create(const char *branch_name)\n+{\n+\tstruct notes_tree_list *ret = (struct notes_tree_list *)\n+\t\txcalloc(sizeof(struct notes_tree_list), 1);\n+\tinit_notes(&ret->tree, branch_name, combine_notes_overwrite,\n+\t\tNOTES_INIT_EMPTY);\n+\tret->next = notes_trees;\n+\tnotes_trees = ret;\n+\treturn &ret->tree;\n+}\n+\n+static struct notes_tree *notes_tree_get(struct branch *b)\n+{\n+\tstruct notes_tree_list *cur = notes_trees;\n+\twhile (cur && strcmp(cur->tree.ref, b->name))\n+\t\tcur = cur->next;\n+\treturn cur ? &cur->tree : NULL;\n+}\n+\n+static void add_to_replace_list(\n+\t\tstruct tree_entry_replace **replace_list,\n+\t\tconst char *old_path, const char *new_path)\n+{\n+\tstruct tree_entry_replace *r = (struct tree_entry_replace *)\n+\t\txmalloc(sizeof(struct tree_entry_replace));\n+\tr->next = (*replace_list)->next;\n+\tr->old_path = xstrdup(old_path);\n+\tr->new_path = xstrdup(new_path);\n+\t(*replace_list)->next = r;\n+\t*replace_list = r;\n+}\n+\n+static void process_replace_list(struct tree_entry_replace *r,\n+\t\tstruct tree_entry *root)\n+{\n+\tstruct tree_entry_replace *tmp;\n+\tstruct tree_entry e;\n+\twhile (r) {\n+\t\tif (r->old_path && r->new_path) {\n+\t\t\t/* rename old_path to new_path */\n+\t\t\tmemset(&e, 0, sizeof(e));\n+\t\t\ttree_content_remove(root, r->old_path, &e);\n+\t\t\tif (!e.versions[1].mode)\n+\t\t\t\tdie(\"Path %s not in branch\", r->old_path);\n+\t\t\ttree_content_set(root, r->new_path, e.versions[1].sha1,\n+\t\t\t\t\t e.versions[1].mode, e.tree);\n+\t\t}\n+\t\ttmp = r;\n+\t\tr = r->next;\n+\t\tfree(tmp);\n+\t}\n+}\n+\n+/*\n+ * Find tree entries below given root that \"match\" the given SHA1 sum, and\n+ * invoke the given callback for each matching entry.\n+ */\n+static int do_for_each_tree_entry_matching_sha1(\n+\t\tstruct tree_entry *root,\n+\t\tconst char *hex_sha1,\n+\t\tunsigned int match_len,\n+\t\tchar *path,\n+\t\tunsigned int path_len,\n+\t\tint cb_fn(struct tree_entry *e, const char *path,\n+\t\t\t  unsigned int path_len, void *cb_data),\n+\t\tvoid *cb_data)\n+{\n+\tstruct tree_content *t = root->tree;\n+\tstruct tree_entry *e;\n+\tunsigned int i;\n+\tint result;\n+\n+\tfor (i = 0; i < t->entry_count; i++) {\n+\t\te = t->entries[i];\n+\t\tif (match_len + e->name->str_len > 40 ||\n+\t\t    memcmp(hex_sha1 + match_len, e->name->str_dat,\n+\t\t\te->name->str_len))\n+\t\t\tcontinue;\n+\n+\t\t/* partial or full match = append to path */\n+\t\tif (path_len)\n+\t\t\tpath[path_len++] = '/';\n+\t\tmemcpy(path + path_len, e->name->str_dat,\n+\t\t\te->name->str_len);\n+\t\tpath_len += e->name->str_len;\n+\t\tassert(path_len < 80);\n+\t\tmatch_len += e->name->str_len;\n+\t\tassert(match_len <= 40);\n+\n+\t\tif (match_len == 40) {\n+\t\t\t/* full match */\n+\t\t\tassert(path_len < 80);\n+\t\t\tpath[path_len] = '\\0';\n+\t\t\tif ((result = cb_fn(e, path, path_len, cb_data)))\n+\t\t\t\treturn result;\n+\t\t}\n+\t\telse {\n+\t\t\t/* partial match */\n+\t\t\tassert(match_len < 40);\n+\t\t\tif (S_ISDIR(e->versions[1].mode)) {\n+\t\t\t\tif (!e->tree)\n+\t\t\t\t\tload_tree(e);\n+\t\t\t\tresult = do_for_each_tree_entry_matching_sha1(\n+\t\t\t\t\te, hex_sha1, match_len, path, path_len,\n+\t\t\t\t\tcb_fn, cb_data);\n+\t\t\t\tif (result)\n+\t\t\t\t\treturn result;\n+\t\t\t}\n+\t\t}\n+\n+\t\tpath_len -= e->name->str_len;\n+\t\tif (path_len)\n+\t\t\tpath_len--;\n+\t\tmatch_len -= e->name->str_len;\n+\t}\n+\treturn 0;\n+}\n+\n+static int for_each_tree_entry_matching_sha1(\n+\t\tstruct tree_entry *root,\n+\t\tconst unsigned char *sha1,\n+\t\tint cb_fn(struct tree_entry *e, const char *path,\n+\t\t\t  unsigned int path_len, void *cb_data),\n+\t\tvoid *cb_data)\n+{\n+\tchar hex_sha1[40], path[80];\n+\tmemcpy(hex_sha1, sha1_to_hex(sha1), 40);\n+\treturn do_for_each_tree_entry_matching_sha1(root, hex_sha1, 0, path, 0,\n+\t\t\t\t\t       cb_fn, cb_data);\n+}\n+\n+static int reconcile_note_and_tree_entry(struct tree_entry *e,\n+\t\tconst char *path, unsigned int path_len, void *cb_data)\n+{\n+\tstruct reconcile_note_and_tree_entry_data *d =\n+\t\t(struct reconcile_note_and_tree_entry_data *) cb_data;\n+\tint same_path = !strcmp(path, d->note_tree_path);\n+\tint same_sha1 = !hashcmp(e->versions[1].sha1, d->note_sha1);\n+\tif (same_sha1 && !same_path)\n+\t\tadd_to_replace_list(d->replace_list, path, d->note_tree_path);\n+\t/* TODO: handle the other combinations of same_path and same_sha1 */\n+\treturn 0;\n+}\n+\n+static int reconcile_notes_tree_helper(\n+\t\tconst unsigned char *object_sha1,\n+\t\tconst unsigned char *note_sha1,\n+\t\tconst char *note_tree_path,\n+\t\tvoid *cb_data)\n+{\n+\t/*\n+\t * Coordinate reconciliation by traversing the tree and invoking the\n+\t * reconcile_note_and_tree_entry() helper for each tree_entry whose\n+\t * path matches the object_sha1 of the current note.\n+\t */\n+\tstruct reconcile_notes_tree_helper_data *d =\n+\t\t(struct reconcile_notes_tree_helper_data *) cb_data;\n+\tstruct reconcile_note_and_tree_entry_data r =\n+\t\t{ object_sha1, note_sha1, note_tree_path, d->replace_list };\n+\treturn for_each_tree_entry_matching_sha1(d->root, object_sha1,\n+\t\treconcile_note_and_tree_entry, &r);\n+}\n+\n+static void do_notes_tree_load(struct tree_entry *root,\n+\t\tconst char *sha1_prefix, unsigned int sha1_prefix_len,\n+\t\tstruct notes_tree *notes)\n+{\n+\tstruct tree_content *t = root->tree;\n+\tunsigned int i;\n+\tchar hex_sha1[40];\n+\n+\tif (sha1_prefix_len)\n+\t\tmemcpy(hex_sha1, sha1_prefix, sha1_prefix_len);\n+\tfor (i = 0; i < t->entry_count; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tstruct tree_entry *e = t->entries[i];\n+\t\tif (e->name->str_len + sha1_prefix_len > 40)\n+\t\t\tcontinue;\n+\t\tmemcpy(hex_sha1 + sha1_prefix_len, e->name->str_dat,\n+\t\t\te->name->str_len);\n+\t\tif (sha1_prefix_len + e->name->str_len == 40 &&\n+\t\t    !get_sha1_hex(hex_sha1, sha1))\n+\t\t\tadd_note(notes, sha1, e->versions[1].sha1, NULL);\n+\t\tif (sha1_prefix_len + e->name->str_len >= 40 ||\n+\t\t    !S_ISDIR(e->versions[1].mode))\n+\t\t\tcontinue;\n+\t\tif (!e->tree)\n+\t\t\tload_tree(e);\n+\t\tdo_notes_tree_load(e, hex_sha1,\n+\t\t\tsha1_prefix_len + e->name->str_len, notes);\n+\t}\n+}\n+\n+static void notes_tree_load(struct notes_tree *notes, struct tree_entry *root)\n+{\n+\tdo_notes_tree_load(root, NULL, 0, notes);\n+}\n+\n+static void notes_tree_unload(const char *branch_name)\n+{\n+\tstruct notes_tree_list *cur = notes_trees, *prev = NULL;\n+\twhile (cur && strcmp(cur->tree.ref, branch_name)) {\n+\t\tprev = cur;\n+\t\tcur = cur->next;\n+\t}\n+\tif (!cur)\n+\t\treturn;\n+\tif (prev)\n+\t\tprev->next = cur->next;\n+\telse\n+\t\tnotes_trees = cur->next;\n+\tfree_notes(&cur->tree);\n+\tfree(cur);\n+}\n+\n static void unload_one_branch(void)\n {\n \twhile (cur_active_branches\n@@ -1844,6 +2092,8 @@ static void unload_one_branch(void)\n \t\t\trelease_tree_content_recursive(e->branch_tree.tree);\n \t\t\te->branch_tree.tree = NULL;\n \t\t}\n+\t\tif (e->has_notes)\n+\t\t\tnotes_tree_unload(e->name);\n \t\tcur_active_branches--;\n \t}\n }\n@@ -2010,7 +2260,7 @@ static void file_change_cr(struct branch *b, int rename)\n \t\tleaf.tree);\n }\n\n-static void note_change_n(struct branch *b)\n+static void note_change_n(struct branch *b, struct notes_tree *notes)\n {\n \tconst char *p = command_buf.buf + 2;\n \tstatic struct strbuf uq = STRBUF_INIT;\n@@ -2080,8 +2330,16 @@ static void note_change_n(struct branch *b)\n \t\t\t    typename(type), command_buf.buf);\n \t}\n\n+\tassert(notes);\n+\tadd_note(notes, commit_sha1, sha1, NULL);\n+\n+\t/*\n+\t * Also add a corresponding entry to the branch_tree. This entry is\n+\t * a placeholder for the final note entry in the tree, and will be\n+\t * renamed into the correct location before committing the tree.\n+\t */\n \ttree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,\n-\t\tS_IFREG | 0644, NULL);\n+\t\t\t S_IFREG | 0644, NULL);\n }\n\n static void file_change_deleteall(struct branch *b)\n@@ -2213,6 +2471,7 @@ static void parse_new_commit(void)\n \tchar *committer = NULL;\n \tstruct hash_list *merge_list = NULL;\n \tunsigned int merge_count;\n+\tstruct notes_tree *notes = NULL;\n\n \t/* Obtain the branch name from the rest of our command */\n \tsp = strchr(command_buf.buf, ' ') + 1;\n@@ -2243,6 +2502,10 @@ static void parse_new_commit(void)\n \t\tload_branch(b);\n \t}\n\n+\t/* retrieve notes tree */\n+\tif (b->has_notes)\n+\t\tnotes = notes_tree_get(b);\n+\n \t/* file_change* */\n \twhile (command_buf.len > 0) {\n \t\tif (!prefixcmp(command_buf.buf, \"M \"))\n@@ -2253,10 +2516,22 @@ static void parse_new_commit(void)\n \t\t\tfile_change_cr(b, 1);\n \t\telse if (!prefixcmp(command_buf.buf, \"C \"))\n \t\t\tfile_change_cr(b, 0);\n-\t\telse if (!prefixcmp(command_buf.buf, \"N \"))\n-\t\t\tnote_change_n(b);\n-\t\telse if (!strcmp(\"deleteall\", command_buf.buf))\n+\t\telse if (!prefixcmp(command_buf.buf, \"N \")) {\n+\t\t\tif (!notes) {\n+\t\t\t\tnotes = notes_tree_create(b->name);\n+\t\t\t\tnotes_tree_load(notes, &b->branch_tree);\n+\t\t\t\tb->has_notes = 1;\n+\t\t\t}\n+\t\t\tnote_change_n(b, notes);\n+\t\t}\n+\t\telse if (!strcmp(\"deleteall\", command_buf.buf)) {\n+\t\t\tif (notes) {\n+\t\t\t\tnotes_tree_unload(b->name);\n+\t\t\t\tb->has_notes = 0;\n+\t\t\t\tnotes = NULL;\n+\t\t\t}\n \t\t\tfile_change_deleteall(b);\n+\t\t}\n \t\telse {\n \t\t\tunread_command_buf = 1;\n \t\t\tbreak;\n@@ -2265,6 +2540,18 @@ static void parse_new_commit(void)\n \t\t\tbreak;\n \t}\n\n+\tif (notes) {\n+\t\t/* reconcile diffs between b->branch_tree and the notes tree */\n+\t\tstruct reconcile_notes_tree_helper_data d;\n+\t\tstruct tree_entry_replace *replace_list =\n+\t\t\txcalloc(1, sizeof(struct tree_entry_replace));\n+\t\tstruct tree_entry_replace *anchor = replace_list;\n+\t\td.root = &b->branch_tree;\n+\t\td.replace_list = &replace_list;\n+\t\tfor_each_note(notes, reconcile_notes_tree_helper, &d);\n+\t\tprocess_replace_list(anchor, &b->branch_tree);\n+\t}\n+\n \t/* build the tree and the commit */\n \tstore_tree(&b->branch_tree);\n \thashcpy(b->branch_tree.versions[0].sha1,\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex b49815d..bf8c509 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -1092,9 +1092,12 @@ test_expect_success 'P: fail on blob mark in gitlink' '\n ### series Q (notes)\n ###\n\n-note1_data=\"Note for the first commit\"\n-note2_data=\"Note for the second commit\"\n-note3_data=\"Note for the third commit\"\n+note1_data=\"The first note for the first commit\"\n+note2_data=\"The first note for the second commit\"\n+note3_data=\"The first note for the third commit\"\n+note1b_data=\"The second note for the first commit\"\n+note1c_data=\"The third note for the first commit\"\n+note2b_data=\"The second note for the second commit\"\n\n test_tick\n cat >input <<INPUT_END\n@@ -1169,7 +1172,45 @@ data <<EOF\n $note3_data\n EOF\n\n+commit refs/notes/foobar\n+mark :10\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes (:10)\n+COMMIT\n+\n+N inline :3\n+data <<EOF\n+$note1b_data\n+EOF\n+\n+commit refs/notes/foobar2\n+mark :11\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes (:11)\n+COMMIT\n+\n+N inline :3\n+data <<EOF\n+$note1c_data\n+EOF\n+\n+commit refs/notes/foobar\n+mark :12\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes (:12)\n+COMMIT\n+\n+deleteall\n+N inline :5\n+data <<EOF\n+$note2b_data\n+EOF\n+\n INPUT_END\n+\n test_expect_success \\\n \t'Q: commit notes' \\\n \t'git fast-import <input &&\n@@ -1224,8 +1265,8 @@ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n notes (:9)\n EOF\n test_expect_success \\\n-\t'Q: verify notes commit' \\\n-\t'git cat-file commit refs/notes/foobar | sed 1d >actual &&\n+\t'Q: verify first notes commit' \\\n+\t'git cat-file commit refs/notes/foobar~2 | sed 1d >actual &&\n \ttest_cmp expect actual'\n\n cat >expect.unsorted <<EOF\n@@ -1235,23 +1276,112 @@ cat >expect.unsorted <<EOF\n EOF\n cat expect.unsorted | sort >expect\n test_expect_success \\\n-\t'Q: verify notes tree' \\\n-\t'git cat-file -p refs/notes/foobar^{tree} | sed \"s/ [0-9a-f]*\t/ /\" >actual &&\n+\t'Q: verify first notes tree' \\\n+\t'git cat-file -p refs/notes/foobar~2^{tree} | sed \"s/ [0-9a-f]*\t/ /\" >actual &&\n \t test_cmp expect actual'\n\n echo \"$note1_data\" >expect\n test_expect_success \\\n-\t'Q: verify note for first commit' \\\n-\t'git cat-file blob refs/notes/foobar:$commit1 >actual && test_cmp expect actual'\n+\t'Q: verify first note for first commit' \\\n+\t'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual'\n\n echo \"$note2_data\" >expect\n test_expect_success \\\n-\t'Q: verify note for second commit' \\\n-\t'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'\n+\t'Q: verify first note for second commit' \\\n+\t'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual'\n+\n+echo \"$note3_data\" >expect\n+test_expect_success \\\n+\t'Q: verify first note for third commit' \\\n+\t'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual'\n+\n+cat >expect <<EOF\n+parent `git rev-parse --verify refs/notes/foobar~2`\n+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\n+notes (:10)\n+EOF\n+test_expect_success \\\n+\t'Q: verify second notes commit' \\\n+\t'git cat-file commit refs/notes/foobar^ | sed 1d >actual &&\n+\ttest_cmp expect actual'\n+\n+cat >expect.unsorted <<EOF\n+100644 blob $commit1\n+100644 blob $commit2\n+100644 blob $commit3\n+EOF\n+cat expect.unsorted | sort >expect\n+test_expect_success \\\n+\t'Q: verify second notes tree' \\\n+\t'git cat-file -p refs/notes/foobar^^{tree} | sed \"s/ [0-9a-f]*\t/ /\" >actual &&\n+\t test_cmp expect actual'\n+\n+echo \"$note1b_data\" >expect\n+test_expect_success \\\n+\t'Q: verify second note for first commit' \\\n+\t'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual'\n+\n+echo \"$note2_data\" >expect\n+test_expect_success \\\n+\t'Q: verify first note for second commit' \\\n+\t'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual'\n\n echo \"$note3_data\" >expect\n test_expect_success \\\n-\t'Q: verify note for third commit' \\\n-\t'git cat-file blob refs/notes/foobar:$commit3 >actual && test_cmp expect actual'\n+\t'Q: verify first note for third commit' \\\n+\t'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual'\n+\n+cat >expect <<EOF\n+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\n+notes (:11)\n+EOF\n+test_expect_success \\\n+\t'Q: verify third notes commit' \\\n+\t'git cat-file commit refs/notes/foobar2 | sed 1d >actual &&\n+\ttest_cmp expect actual'\n+\n+cat >expect.unsorted <<EOF\n+100644 blob $commit1\n+EOF\n+cat expect.unsorted | sort >expect\n+test_expect_success \\\n+\t'Q: verify third notes tree' \\\n+\t'git cat-file -p refs/notes/foobar2^{tree} | sed \"s/ [0-9a-f]*\t/ /\" >actual &&\n+\t test_cmp expect actual'\n+\n+echo \"$note1c_data\" >expect\n+test_expect_success \\\n+\t'Q: verify third note for first commit' \\\n+\t'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual'\n+\n+cat >expect <<EOF\n+parent `git rev-parse --verify refs/notes/foobar^`\n+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\n+notes (:12)\n+EOF\n+test_expect_success \\\n+\t'Q: verify fourth notes commit' \\\n+\t'git cat-file commit refs/notes/foobar | sed 1d >actual &&\n+\ttest_cmp expect actual'\n+\n+cat >expect.unsorted <<EOF\n+100644 blob $commit2\n+EOF\n+cat expect.unsorted | sort >expect\n+test_expect_success \\\n+\t'Q: verify fourth notes tree' \\\n+\t'git cat-file -p refs/notes/foobar^{tree} | sed \"s/ [0-9a-f]*\t/ /\" >actual &&\n+\t test_cmp expect actual'\n+\n+echo \"$note2b_data\" >expect\n+test_expect_success \\\n+\t'Q: verify second note for second commit' \\\n+\t'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'\n\n test_done\n--\n1.6.4.304.g1365c.dirty\n"},{"id":"127961","messageId":"1258681154-2167-10-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 09/10] Rename t9301 to t9350, to make room for more fast-import tests","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:13Z","receivedAt":"2009-11-20T01:39: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 t/{t9301-fast-export.sh => t9350-fast-export.sh} |    0\n 1 files changed, 0 insertions(+), 0 deletions(-)\n rename t/{t9301-fast-export.sh => t9350-fast-export.sh} (100%)\n\ndiff --git a/t/t9301-fast-export.sh b/t/t9350-fast-export.sh\nsimilarity index 100%\nrename from t/t9301-fast-export.sh\nrename to t/t9350-fast-export.sh\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"127962","messageId":"1258681154-2167-11-git-send-email-johan@herland.net","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv8 10/10] Add more testcases to test fast-import of notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:39:14Z","receivedAt":"2009-11-20T01:39:14Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This patch adds testcases verifying correct behaviour in several scenarios\nregarding fast-import of notes:\n- using a mixture of 'N' and 'M' commands\n- updating existing notes\n- concatenation of notes\n- 'deleteall' also removes notes\n- fanout schemes is added/removed when needed\n- git-fast-import's branch unload/reload preserves notes\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n t/t9301-fast-import-notes.sh |  578 ++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 578 insertions(+), 0 deletions(-)\n create mode 100755 t/t9301-fast-import-notes.sh\n\ndiff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh\nnew file mode 100755\nindex 0000000..5a08a76\n--- /dev/null\n+++ b/t/t9301-fast-import-notes.sh\n@@ -0,0 +1,578 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2009 Johan Herland\n+#\n+\n+test_description='test git fast-import of notes objects'\n+. ./test-lib.sh\n+\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/heads/master\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+first commit\n+COMMIT\n+\n+M 644 inline foo\n+data <<EOF\n+file foo in first commit\n+EOF\n+\n+M 755 inline bar\n+data <<EOF\n+file bar in first commit\n+EOF\n+\n+M 644 inline baz/xyzzy\n+data <<EOF\n+file baz/xyzzy in first commit\n+EOF\n+\n+commit refs/heads/master\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+second commit\n+COMMIT\n+\n+M 644 inline foo\n+data <<EOF\n+file foo in second commit\n+EOF\n+\n+M 755 inline baz/xyzzy\n+data <<EOF\n+file baz/xyzzy in second commit\n+EOF\n+\n+commit refs/heads/master\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+third commit\n+COMMIT\n+\n+M 644 inline foo\n+data <<EOF\n+file foo in third commit\n+EOF\n+\n+commit refs/heads/master\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+fourth commit\n+COMMIT\n+\n+M 755 inline bar\n+data <<EOF\n+file bar in fourth commit\n+EOF\n+\n+INPUT_END\n+\n+test_expect_success 'set up master branch' '\n+\n+\tgit fast-import <input &&\n+\tgit whatchanged master\n+'\n+\n+commit4=$(git rev-parse refs/heads/master)\n+commit3=$(git rev-parse \"$commit4^\")\n+commit2=$(git rev-parse \"$commit4~2\")\n+commit1=$(git rev-parse \"$commit4~3\")\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+first notes commit\n+COMMIT\n+\n+M 644 inline $commit1\n+data <<EOF\n+first note for first commit\n+EOF\n+\n+M 755 inline $commit2\n+data <<EOF\n+first note for second commit\n+EOF\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    third commit\n+    second commit\n+    first note for second commit\n+    first commit\n+    first note for first commit\n+EXPECT_END\n+\n+test_expect_success 'add notes with simple M command' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+second notes commit\n+COMMIT\n+\n+from refs/notes/test^0\n+N inline $commit3\n+data <<EOF\n+first note for third commit\n+EOF\n+\n+N inline $commit4\n+data <<EOF\n+first note for fourth commit\n+EOF\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    first note for fourth commit\n+    third commit\n+    first note for third commit\n+    second commit\n+    first note for second commit\n+    first commit\n+    first note for first commit\n+EXPECT_END\n+\n+test_expect_success 'add notes with simple N command' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+third notes commit\n+COMMIT\n+\n+from refs/notes/test^0\n+N inline $commit1\n+data <<EOF\n+second note for first commit\n+EOF\n+\n+N inline $commit2\n+data <<EOF\n+second note for second commit\n+EOF\n+\n+N inline $commit3\n+data <<EOF\n+second note for third commit\n+EOF\n+\n+N inline $commit4\n+data <<EOF\n+second note for fourth commit\n+EOF\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    second note for fourth commit\n+    third commit\n+    second note for third commit\n+    second commit\n+    second note for second commit\n+    first commit\n+    second note for first commit\n+EXPECT_END\n+\n+test_expect_success 'update existing notes with N command' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+fourth notes commit\n+COMMIT\n+\n+from refs/notes/test^0\n+M 644 inline $(echo \"$commit3\" | sed \"s|^..|&/|\")\n+data <<EOF\n+prefix of note for third commit\n+EOF\n+\n+M 644 inline $(echo \"$commit4\" | sed \"s|^..|&/|\")\n+data <<EOF\n+prefix of note for fourth commit\n+EOF\n+\n+M 644 inline $(echo \"$commit4\" | sed \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\")\n+data <<EOF\n+pre-prefix of note for fourth commit\n+EOF\n+\n+N inline $commit1\n+data <<EOF\n+third note for first commit\n+EOF\n+\n+N inline $commit2\n+data <<EOF\n+third note for second commit\n+EOF\n+\n+N inline $commit3\n+data <<EOF\n+third note for third commit\n+EOF\n+\n+N inline $commit4\n+data <<EOF\n+third note for fourth commit\n+EOF\n+\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    pre-prefix of note for fourth commit\n+    prefix of note for fourth commit\n+    third note for fourth commit\n+    third commit\n+    prefix of note for third commit\n+    third note for third commit\n+    second commit\n+    third note for second commit\n+    first commit\n+    third note for first commit\n+EXPECT_END\n+\n+test_expect_success 'add concatentation notes with M command' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+fifth notes commit\n+COMMIT\n+\n+from refs/notes/test^0\n+deleteall\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    third commit\n+    second commit\n+    first commit\n+EXPECT_END\n+\n+test_expect_success 'verify that deleteall also removes notes' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/test\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+sixth notes commit\n+COMMIT\n+\n+from refs/notes/test^0\n+M 644 inline $commit1\n+data <<EOF\n+third note for first commit\n+EOF\n+\n+M 644 inline $commit3\n+data <<EOF\n+third note for third commit\n+EOF\n+\n+N inline $commit1\n+data <<EOF\n+fourth note for first commit\n+EOF\n+\n+N inline $commit3\n+data <<EOF\n+fourth note for third commit\n+EOF\n+\n+INPUT_END\n+\n+cat >expect <<EXPECT_END\n+    fourth commit\n+    third commit\n+    fourth note for third commit\n+    second commit\n+    first commit\n+    fourth note for first commit\n+EXPECT_END\n+\n+test_expect_success 'verify that N commands override M commands' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/test git log | grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+# Write fast-import commands to create the given number of commits\n+fast_import_commits () {\n+\tmy_ref=$1\n+\tmy_num_commits=$2\n+\tmy_append_to_file=$3\n+\tmy_i=0\n+\twhile test $my_i -lt $my_num_commits\n+\tdo\n+\t\tmy_i=$(($my_i + 1))\n+\t\ttest_tick\n+\t\tcat >>\"$my_append_to_file\" <<INPUT_END\n+commit $my_ref\n+mark :$my_i\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+commit #$my_i\n+COMMIT\n+\n+M 644 inline file\n+data <<EOF\n+file contents in commit #$my_i\n+EOF\n+\n+INPUT_END\n+\tdone\n+}\n+\n+# Write fast-import commands to create the given number of notes annotating\n+# the commits created by fast_import_commits()\n+fast_import_notes () {\n+\tmy_notes_ref=$1\n+\tmy_num_commits=$2\n+\tmy_append_to_file=$3\n+\tmy_note_append=$4\n+\ttest_tick\n+\tcat >>\"$my_append_to_file\" <<INPUT_END\n+commit $my_notes_ref\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+committing $my_num_commits notes\n+COMMIT\n+\n+INPUT_END\n+\n+\tmy_i=0\n+\twhile test $my_i -lt $my_num_commits\n+\tdo\n+\t\tmy_i=$(($my_i + 1))\n+\t\tcat >>\"$my_append_to_file\" <<INPUT_END\n+N inline :$my_i\n+data <<EOF\n+note for commit #$my_i$my_note_append\n+EOF\n+\n+INPUT_END\n+\tdone\n+}\n+\n+\n+rm input expect\n+num_commits=400\n+# Create lots of commits\n+fast_import_commits \"refs/heads/many_commits\" $num_commits input\n+# Create one note per above commit\n+fast_import_notes \"refs/notes/many_notes\" $num_commits input\n+# Finally create the expected output from all these notes and commits\n+i=$num_commits\n+while test $i -gt 0\n+do\n+\tcat >>expect <<EXPECT_END\n+    commit #$i\n+    note for commit #$i\n+EXPECT_END\n+\ti=$(($i - 1))\n+done\n+\n+test_expect_success 'add lots of commits and notes' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |\n+\t    grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_expect_success 'verify that lots of notes trigger a fanout scheme' '\n+\n+\t# None of the entries in the top-level notes tree should be a full SHA1\n+\tgit cat-file -p refs/notes/many_notes: |\n+\twhile read mode type sha1 path\n+\tdo\n+\t\tpath_len=$(expr length \"$path\") &&\n+\t\tif test $path_len -ge 40\n+\t\tthen\n+\t\t\treturn 1\n+\t\tfi\n+\tdone\n+\n+'\n+\n+commit1=$(git rev-parse \"refs/heads/many_commits\")\n+commit2=$(git rev-parse \"$commit1^\")\n+commit3=$(git rev-parse \"$commit2^\")\n+commit4=$(git rev-parse \"$commit3^\")\n+\n+test_tick\n+cat >input <<INPUT_END\n+commit refs/notes/many_notes\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+replacing many notes with few notes\n+COMMIT\n+\n+from refs/notes/many_notes^0\n+deleteall\n+N inline $commit1\n+data <<EOF\n+note for commit #$num_commits\n+EOF\n+\n+N inline $commit2\n+data <<EOF\n+note for commit #$(($num_commits - 1))\n+EOF\n+\n+N inline $commit3\n+data <<EOF\n+note for commit #$(($num_commits - 2))\n+EOF\n+\n+N inline $commit4\n+data <<EOF\n+note for commit #$(($num_commits - 3))\n+EOF\n+\n+INPUT_END\n+\n+i=$num_commits\n+rm expect\n+while test $i -gt 0\n+do\n+\tcat >>expect <<EXPECT_END\n+    commit #$i\n+EXPECT_END\n+\tif test $i -gt $(($num_commits - 4))\n+\tthen\n+\t\tcat >>expect <<EXPECT_END\n+    note for commit #$i\n+EXPECT_END\n+\tfi\n+\ti=$(($i - 1))\n+done\n+\n+test_expect_success 'remove lots of notes' '\n+\n+\tgit fast-import <input &&\n+\tGIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |\n+\t    grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_expect_success 'verify that removing notes trigger fanout consolidation' '\n+\n+\t# All entries in the top-level notes tree should be a full SHA1\n+\tgit cat-file -p refs/notes/many_notes: |\n+\twhile read mode type sha1 path\n+\tdo\n+\t\tpath_len=$(expr length \"$path\") &&\n+\t\tif test $path_len -ne 40\n+\t\tthen\n+\t\t\treturn 1\n+\t\tfi\n+\tdone\n+\n+'\n+\n+rm input expect\n+num_notes_refs=10\n+num_commits=16\n+some_commits=8\n+# Create commits\n+fast_import_commits \"refs/heads/more_commits\" $num_commits input\n+# Create one note per above commit per notes ref\n+i=0\n+while test $i -lt $num_notes_refs\n+do\n+\ti=$(($i + 1))\n+\tfast_import_notes \"refs/notes/more_notes_$i\" $num_commits input\n+done\n+# Trigger branch reloading in git-fast-import by repeating the note creation\n+i=0\n+while test $i -lt $num_notes_refs\n+do\n+\ti=$(($i + 1))\n+\tfast_import_notes \"refs/notes/more_notes_$i\" $some_commits input \" (2)\"\n+done\n+# Finally create the expected output from the notes in refs/notes/more_notes_1\n+i=$num_commits\n+while test $i -gt 0\n+do\n+\tnote_data=\"note for commit #$i\"\n+\tif test $i -le $some_commits\n+\tthen\n+\t\tnote_data=\"$note_data (2)\"\n+\tfi\n+\tcat >>expect <<EXPECT_END\n+    commit #$i\n+    $note_data\n+EXPECT_END\n+\ti=$(($i - 1))\n+done\n+\n+test_expect_success \"add notes to $num_commits commits in each of $num_notes_refs refs\" '\n+\n+\tgit fast-import --active-branches=5 <input &&\n+\tGIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits |\n+\t    grep \"^    \" > actual &&\n+\ttest_cmp expect actual\n+\n+'\n+\n+test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"127990","messageId":"7vk4xl5y3z.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"1258681154-2167-1-git-send-email-johan@herland.net","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-20T09:44:48Z","receivedAt":"2009-11-20T09:44:48Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johan Herland <johan@herland.net> writes:\n\n> Here is the 8th iteration of the git-notes series. Changes in this\n> iteration are as follows:\n>\n> Changes to existing patches:\n> - Rebased onto current 'next', dropping the early part of this series\n>   which has now been merged to 'next'.\n> - Patch 8 (was patch 22): Major rewrite of fast-import's notes handling\n>   code based on comments from Shawn.\n>\n> New patches:\n> - Patch 9: Rename t9301 to t9350, to make room for more fast-import tests\n> - Patch 10: More fast-import tests\n>\n> TODO:\n> - Builtin-ify git-notes shell script to take advantage of notes API\n> - Garbage collect notes whose referenced object is unreachable (gc_notes())\n> - Handle note objects that are not blobs, but trees\n\nThanks.\n\nWhile I try my best not to break other people's patches while applying, I\nprefer to see a re-rolled series based on the same commit while replacing\nan existing series, unless the re-roll truly depends on the newer base, so\nthat people can more easily see what got updated.\n\nSo here is what I did tonight.\n\nStep 0.  Apply as you specified on top of 'next'\n\n    $ git checkout next^0\n    $ git am -s your-10-patches\n    $ M=$(git describe)\n\nStep 1.  Rebase back to the bottom of the old series\n\n    $ git checkout next...jh/notes\n    $ git rebase --onto HEAD next $M\n    $ N=$(git describe)\n\nStep 2.  Compare old and new series\n\n    $ git show-branch jh/notes HEAD\n    $ for i in 7 6 5 4 3 2 1 0\n      do\n        git diff jh/notes~$i HEAD~$(( $i + 2 ))\n      done\n    $ make -j test\n\nStep 3.  Make sure the result is what you intended to feed me.\n\n    $ git merge next\n    $ git diff $M\n\nStep 4.  Replace the tip of jh/notes\n\n    $ git branch -f jh/notes $N\n"},{"id":"127993","messageId":"200911201114.29331.johan@herland.net","threadId":"21682","inReplyTo":"7vk4xl5y3z.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T10:14:29Z","receivedAt":"2009-11-20T10:14:29Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Friday 20 November 2009, Junio C Hamano wrote:\n> Johan Herland <johan@herland.net> writes:\n> > Here is the 8th iteration of the git-notes series.\n>\n> Thanks.\n>\n> While I try my best not to break other people's patches while\n> applying, I prefer to see a re-rolled series based on the same commit\n> while replacing an existing series, unless the re-roll truly depends\n> on the newer base, so that people can more easily see what got\n> updated.\n\nAh, I see. I was thinking I should do the rebase to resolve any \nconflicts before sending it to you, but I now see that - in the case \nwhere there are no conflicts - the non-rebased version is actually \npreferable. I will try to keep that in mind in the future.\n\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"127996","messageId":"20091120192800.6117@nanako3.lavabit.com","threadId":"21682","inReplyTo":"7vk4xl5y3z.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Nanako Shiraishi","fromEmail":"nanako3@lavabit.com","sentAt":null,"receivedAt":"2009-11-20T10:26:42Z","isPatch":false,"sender":{"key":"nanako3@lavabit.com","avatar":"https://gravatar.com/avatar/3777b9e201c5883a62b1a6fdf7c53f2d712d1d80989146063ea861e33aad72a8?d=mp&s=160"},"body":"Quoting Junio C Hamano <gitster@pobox.com>\n\n> So here is what I did tonight.\n>\n> Step 0.  Apply as you specified on top of 'next'\n>\n>     $ git checkout next^0\n>     $ git am -s your-10-patches\n>     $ M=$(git describe)\n>\n> Step 1.  Rebase back to the bottom of the old series\n>\n>     $ git checkout next...jh/notes\n\nWhat do three-dots mean in this command?\n\n>     $ git rebase --onto HEAD next $M\n>     $ N=$(git describe)\n\n-- \nNanako Shiraishi\nhttp://ivory.ap.teacup.com/nanako3/\n"},{"id":"127997","messageId":"alpine.DEB.1.00.0911201135230.4985@pacific.mpi-cbg.de","threadId":"21682","inReplyTo":"20091120192800.6117@nanako3.lavabit.com","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2009-11-20T10:36:13Z","receivedAt":"2009-11-20T10:36:13Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 20 Nov 2009, Nanako Shiraishi wrote:\n\n> Quoting Junio C Hamano <gitster@pobox.com>\n> \n> > So here is what I did tonight.\n> >\n> > Step 0.  Apply as you specified on top of 'next'\n> >\n> >     $ git checkout next^0\n> >     $ git am -s your-10-patches\n> >     $ M=$(git describe)\n> >\n> > Step 1.  Rebase back to the bottom of the old series\n> >\n> >     $ git checkout next...jh/notes\n> \n> What do three-dots mean in this command?\n\nMaybe it means \"go back to the bottom of the old series\"? ;-)  IIUC it is \nthe equivalent of\n\n\t$ git checkout $(git merge-base next jh/notes)\n\nHth,\nDscho\n"},{"id":"127998","messageId":"7vk4xl1nkl.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"20091120192800.6117@nanako3.lavabit.com","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-20T10:46:02Z","receivedAt":"2009-11-20T10:46:02Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nanako Shiraishi <nanako3@lavabit.com> writes:\n\n>> Step 0.  Apply as you specified on top of 'next'\n>>\n>>     $ git checkout next^0\n>>     $ git am -s your-10-patches\n>>     $ M=$(git describe)\n>>\n>> Step 1.  Rebase back to the bottom of the old series\n>>\n>>     $ git checkout next...jh/notes\n>\n> What do three-dots mean in this command?\n>\n>>     $ git rebase --onto HEAD next $M\n>>     $ N=$(git describe)\n\nHeh, good eyes.\n\nI forgot that I had this toy hidden from the general public.\n\n-- >8 --\nSubject: [PATCH] \"checkout A...B\" switches to the merge base between A and B\n\nWhen flipping commits around on topic branches, I often end up doing\nthis sequence:\n\n * Run \"log --oneline next..jc/frotz\" to find out the first commit\n   on 'jc/frotz' branch not yet merged to 'next';\n\n * Run \"checkout $that_commit^\" to detach HEAD to the parent of it;\n\n * Rebuild the series on top of that commit; and\n\n * \"show-branch jc/frotz HEAD\" and \"diff jc/frotz HEAD\" to verify.\n\nIntroduce a new syntax to \"git checkout\" to name the commit to switch to,\nto make the first two steps easier.  When the branch to switch to is\nspecified as A...B (you can omit either A or B but not both, and HEAD\nis used instead of the omitted side), the merge base between these two\ncommits are computed, and if there is one unique one, we detach the HEAD\nat that commit.\n\nWith this, I can say \"checkout next...jc/frotz\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin-checkout.c       |    7 +++++--\n cache.h                  |    1 +\n sha1_name.c              |   42 ++++++++++++++++++++++++++++++++++++++++++\n t/t2012-checkout-last.sh |   27 ++++++++++++++++++++++++++-\n 4 files changed, 74 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex 64f3a11..7167111 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -690,7 +690,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t * case 3: git checkout <something> [<paths>]\n \t *\n \t *   With no paths, if <something> is a commit, that is to\n-\t *   switch to the branch or detach HEAD at it.\n+\t *   switch to the branch or detach HEAD at it.  As a special case,\n+\t *   if <something> is A...B (missing A or B means HEAD but you can\n+\t *   omit at most one side), and if there is a unique merge base\n+\t *   between A and B, A...B names that merge base.\n \t *\n \t *   With no paths, if <something> is _not_ a commit, no -t nor -b\n \t *   was given, and there is a tracking branch whose name is\n@@ -716,7 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!strcmp(arg, \"-\"))\n \t\t\targ = \"@{-1}\";\n \n-\t\tif (get_sha1(arg, rev)) {\n+\t\tif (get_sha1_mb(arg, rev)) {\n \t\t\tif (has_dash_dash)          /* case (1) */\n \t\t\t\tdie(\"invalid reference: %s\", arg);\n \t\t\tif (!patch_mode &&\ndiff --git a/cache.h b/cache.h\nindex 71a731d..f8df15a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -703,6 +703,7 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *\n extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);\n extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);\n extern int interpret_branch_name(const char *str, struct strbuf *);\n+extern int get_sha1_mb(const char *str, unsigned char *sha1);\n \n extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);\n extern const char *ref_rev_parse_rules[];\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 44bb62d..d5a240c 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -794,6 +794,48 @@ release_return:\n \treturn retval;\n }\n \n+int get_sha1_mb(const char *name, unsigned char *sha1)\n+{\n+\tstruct commit *one, *two;\n+\tstruct commit_list *mbs;\n+\tunsigned char sha1_tmp[20];\n+\tconst char *dots;\n+\tint st;\n+\n+\tdots = strstr(name, \"...\");\n+\tif (!dots)\n+\t\treturn get_sha1(name, sha1);\n+\tif (dots == name)\n+\t\tst = get_sha1(\"HEAD\", sha1_tmp);\n+\telse {\n+\t\tstruct strbuf sb;\n+\t\tstrbuf_init(&sb, dots - name);\n+\t\tstrbuf_add(&sb, name, dots - name);\n+\t\tst = get_sha1(sb.buf, sha1_tmp);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\tif (st)\n+\t\treturn st;\n+\tone = lookup_commit_reference_gently(sha1_tmp, 0);\n+\tif (!one)\n+\t\treturn -1;\n+\n+\tif (get_sha1(dots[3] ? (dots + 3) : \"HEAD\", sha1_tmp))\n+\t\treturn -1;\n+\ttwo = lookup_commit_reference_gently(sha1_tmp, 0);\n+\tif (!two)\n+\t\treturn -1;\n+\tmbs = get_merge_bases(one, two, 1);\n+\tif (!mbs || mbs->next)\n+\t\tst = -1;\n+\telse {\n+\t\tst = 0;\n+\t\thashcpy(sha1, mbs->item->object.sha1);\n+\t}\n+\tfree_commit_list(mbs);\n+\treturn st;\n+}\n+\n /*\n  * This is like \"get_sha1_basic()\", except it allows \"sha1 expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nindex 87b30a2..b44de9d 100755\n--- a/t/t2012-checkout-last.sh\n+++ b/t/t2012-checkout-last.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='checkout can switch to last branch'\n+test_description='checkout can switch to last branch and merge base'\n \n . ./test-lib.sh\n \n@@ -91,4 +91,29 @@ test_expect_success 'switch to twelfth from the last' '\n \ttest \"z$(git symbolic-ref HEAD)\" = \"zrefs/heads/branch13\"\n '\n \n+test_expect_success 'merge base test setup' '\n+\tgit checkout -b another other &&\n+\techo \"hello again\" >>world &&\n+\tgit add world &&\n+\tgit commit -m third\n+'\n+\n+test_expect_success 'another...master' '\n+\tgit checkout another &&\n+\tgit checkout another...master &&\n+\ttest \"z$(git rev-parse --verify HEAD)\" = \"z$(git rev-parse --verify master^)\"\n+'\n+\n+test_expect_success '...master' '\n+\tgit checkout another &&\n+\tgit checkout ...master &&\n+\ttest \"z$(git rev-parse --verify HEAD)\" = \"z$(git rev-parse --verify master^)\"\n+'\n+\n+test_expect_success 'master...' '\n+\tgit checkout another &&\n+\tgit checkout master... &&\n+\ttest \"z$(git rev-parse --verify HEAD)\" = \"z$(git rev-parse --verify master^)\"\n+'\n+\n test_done\n-- \n1.6.5.3.342.g14bb9\n"},{"id":"128002","messageId":"7veintzcff.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"7vk4xl1nkl.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-11-20T11:02:44Z","receivedAt":"2009-11-20T11:02:44Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"By the way, we could probably use this to make the command sequence even\nshorter.\n\nI have to warn that this has never been used in real life, unlike the\n\"checkout A...B\" one --- caveat emptor.\n\nAlso I didn't bother touching up \"rebase -i\"; it is left as an excercise\nto the readers ;-)\n\n-- >8 --\nSubject: [PATCH] \"rebase --onto A...B\" replays history on the merge base between A and B\n\nThis is in spirit similar to \"checkout A...B\".  To re-queue a new set of\npatches for a series that the original author prepared to apply on 'next'\non the same base as before, you would do something like this:\n\n    $ git checkout next^0\n    $ git am -s rerolled-series.mbox\n    $ git rebase --onto next...jh/notes next\n\nThe first two commands recreates commits to be rebased as the original\nauthor intended (i.e. applies directly on top of 'next'), and the rebase\ncommand replays that history on top of the same commit the series being\nreplaced was built on (which is typically much older than the tip of\n'next').\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n git-rebase.sh |   19 ++++++++++++++++++-\n 1 files changed, 18 insertions(+), 1 deletions(-)\n\ndiff --git a/git-rebase.sh b/git-rebase.sh\nindex 6830e16..570e2b6 100755\n--- a/git-rebase.sh\n+++ b/git-rebase.sh\n@@ -34,6 +34,8 @@ set_reflog_action rebase\n require_work_tree\n cd_to_toplevel\n \n+LF='\n+'\n OK_TO_SKIP_PRE_REBASE=\n RESOLVEMSG=\"\n When you have resolved this problem run \\\"git rebase --continue\\\".\n@@ -417,7 +419,22 @@ fi\n \n # Make sure the branch to rebase onto is valid.\n onto_name=${newbase-\"$upstream_name\"}\n-onto=$(git rev-parse --verify \"${onto_name}^0\") || exit\n+if\tleft=$(expr \"$onto_name\" : '\\(.*\\)\\.\\.\\.') &&\n+\tright=$(expr \"$onto_name\" : '\\.\\.\\.\\(.*\\)$') &&\n+\t: ${left:=HEAD} ${right:=HEAD} &&\n+\tonto=$(git merge-base \"$left\" \"$right\")\n+then\n+\tcase \"$onto\" in\n+\t?*\"$LF\"?*)\n+\t\tdie \"$onto_name: there are more than one merge bases\"\n+\t\t;;\n+\t'')\n+\t\tdie \"$onto_name: there is no merge base\"\n+\t\t;;\n+\tesac\n+else\n+\tonto=$(git rev-parse --verify \"${onto_name}^0\") || exit\n+fi\n \n # If a hook exists, give it a chance to interrupt\n run_pre_rebase_hook \"$upstream_arg\" \"$@\"\n-- \n1.6.5.3.342.g14bb9\n"},{"id":"128431","messageId":"20091126024655.GR11919@spearce.org","threadId":"21682","inReplyTo":"1258681154-2167-9-git-send-email-johan@herland.net","subject":"Re: [RFC/PATCHv8 08/10] fast-import: Proper notes tree manipulation using the notes API","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-11-26T02:46:55Z","receivedAt":"2009-11-26T02:46:55Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Johan Herland <johan@herland.net> wrote:\n> This patch teaches 'git fast-import' to use the notes API to organize\n...\n> This patch is substantially different from the previous iteration.\n> Unloading (and reloading) the notes tree along with its corresponding\n> branch was relatively straightforward to fix, but avoiding the\n> destroying and re-adding of all the notes in every commit was much\n> harder. After 3-4 attempts at a simpler (but fundamentally broken)\n> approach, I finally landed on this. I'm not satisfied with the amount\n> of code introduced by this patch, and would be happy if someone found\n> a better/shorter/more elegant way to solve this problem.\n\nYea, I agree, I'm not happy with the amount of complex code added\nto implement this.  But I can't say there's a better way to do it\nand still reuse the notes code.  Maybe its just worth breaking away\nfrom the notes code altogether?  fast-import also implements its\nown pack formatting functions because reusing them from pack-objects\nwas just too ugly.\n\nAside from a few minor nits below though, I could ACK this, it at\nleast avoids the nasty corners that can arise when there are a lot\nof branches and tries to minimize the cost when there are many notes.\n \n> diff --git a/fast-import.c b/fast-import.c\n> +\n> +static void add_to_replace_list(\n> +\t\tstruct tree_entry_replace **replace_list,\n> +\t\tconst char *old_path, const char *new_path)\n> +{\n> +\tstruct tree_entry_replace *r = (struct tree_entry_replace *)\n> +\t\txmalloc(sizeof(struct tree_entry_replace));\n> +\tr->next = (*replace_list)->next;\n> +\tr->old_path = xstrdup(old_path);\n> +\tr->new_path = xstrdup(new_path);\n> +\t(*replace_list)->next = r;\n> +\t*replace_list = r;\n\nReally?  I don't get why you are replacing the head's next with r\nonly to then replace head itself with r.\n\n> @@ -2265,6 +2540,18 @@ static void parse_new_commit(void)\n>  \t\t\tbreak;\n>  \t}\n> \n> +\tif (notes) {\n> +\t\t/* reconcile diffs between b->branch_tree and the notes tree */\n> +\t\tstruct reconcile_notes_tree_helper_data d;\n> +\t\tstruct tree_entry_replace *replace_list =\n> +\t\t\txcalloc(1, sizeof(struct tree_entry_replace));\n\nOh, I see.  The issue I had with understanding add_to_replace_list()\nis due to this spot allocating a blank header node.  Normally we do\nthis with a pointer to a pointer and initialize NULL:\n\n\tstruct tree_entry_replace *list = NULL;\n\tstruct tree_entry_replace **replace_list = &list;\n\nCan we avoid this blank header node?  I think it comlicates the code,\ne.g. in process_replace_list() you have to skip over the blank node\nby testing for both paths being NULL.\n\n-- \nShawn.\n"},{"id":"128452","messageId":"200911261210.48138.johan@herland.net","threadId":"21682","inReplyTo":"20091126024655.GR11919@spearce.org","subject":"Re: [RFC/PATCHv8 08/10] fast-import: Proper notes tree manipulation using the notes API","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-26T11:10:47Z","receivedAt":"2009-11-26T11:10:47Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Thursday 26 November 2009, Shawn O. Pearce wrote:\n> Johan Herland <johan@herland.net> wrote:\n> > This patch teaches 'git fast-import' to use the notes API to\n> > organize\n>\n> ...\n>\n> > This patch is substantially different from the previous iteration.\n> > Unloading (and reloading) the notes tree along with its\n> > corresponding branch was relatively straightforward to fix, but\n> > avoiding the destroying and re-adding of all the notes in every\n> > commit was much harder. After 3-4 attempts at a simpler (but\n> > fundamentally broken) approach, I finally landed on this. I'm not\n> > satisfied with the amount of code introduced by this patch, and\n> > would be happy if someone found a better/shorter/more elegant way\n> > to solve this problem.\n>\n> Yea, I agree, I'm not happy with the amount of complex code added\n> to implement this.  But I can't say there's a better way to do it\n> and still reuse the notes code.  Maybe its just worth breaking away\n> from the notes code altogether?  fast-import also implements its\n> own pack formatting functions because reusing them from pack-objects\n> was just too ugly.\n\nOk, I will attempt to redo the patch without reusing the notes code. I \nhave couple of ideas on how to get it done, but probably won't have the \ntime to implement them until next week...\n\n> Aside from a few minor nits below though, I could ACK this, it at\n> least avoids the nasty corners that can arise when there are a lot\n> of branches and tries to minimize the cost when there are many notes.\n>\n> > diff --git a/fast-import.c b/fast-import.c\n> > +\n> > +static void add_to_replace_list(\n> > +\t\tstruct tree_entry_replace **replace_list,\n> > +\t\tconst char *old_path, const char *new_path)\n> > +{\n> > +\tstruct tree_entry_replace *r = (struct tree_entry_replace *)\n> > +\t\txmalloc(sizeof(struct tree_entry_replace));\n> > +\tr->next = (*replace_list)->next;\n> > +\tr->old_path = xstrdup(old_path);\n> > +\tr->new_path = xstrdup(new_path);\n> > +\t(*replace_list)->next = r;\n> > +\t*replace_list = r;\n>\n> Really?  I don't get why you are replacing the head's next with r\n> only to then replace head itself with r.\n>\n> > @@ -2265,6 +2540,18 @@ static void parse_new_commit(void)\n> >  \t\t\tbreak;\n> >  \t}\n> >\n> > +\tif (notes) {\n> > +\t\t/* reconcile diffs between b->branch_tree and the notes tree */\n> > +\t\tstruct reconcile_notes_tree_helper_data d;\n> > +\t\tstruct tree_entry_replace *replace_list =\n> > +\t\t\txcalloc(1, sizeof(struct tree_entry_replace));\n>\n> Oh, I see.  The issue I had with understanding add_to_replace_list()\n> is due to this spot allocating a blank header node.  Normally we do\n> this with a pointer to a pointer and initialize NULL:\n>\n> \tstruct tree_entry_replace *list = NULL;\n> \tstruct tree_entry_replace **replace_list = &list;\n>\n> Can we avoid this blank header node?  I think it comlicates the code,\n> e.g. in process_replace_list() you have to skip over the blank node\n> by testing for both paths being NULL.\n\nThe main problem is that I need to grow a linked list at the far end, \nwhile keeping a reference to the first element, so that \nprocess_replace_list() traverse the list in the correct order (FIFO). \nHowever, I agree that my approach to doing so may have been somewhat \nham-fisted... Will redo (unless the alternative approach above renders \nthis code obsolete).\n\nBTW, while we're on the topic, this whole code is only present because I \nassume it's not possible to edit the fast-import tree structure _while_ \ntraversing it. Is this assumption correct, or are there ways to get \naround maintaining a separate edit list that is applied to the tree \nstructure afterwards?\n\n\nThanks for the review! :)\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"128493","messageId":"20091126193323.GU11919@spearce.org","threadId":"21682","inReplyTo":"200911261210.48138.johan@herland.net","subject":"Re: [RFC/PATCHv8 08/10] fast-import: Proper notes tree manipulation using the notes API","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-11-26T19:33:23Z","receivedAt":"2009-11-26T19:33:23Z","isPatch":false,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Johan Herland <johan@herland.net> wrote:\n> BTW, while we're on the topic, this whole code is only present because I \n> assume it's not possible to edit the fast-import tree structure _while_ \n> traversing it. Is this assumption correct, or are there ways to get \n> around maintaining a separate edit list that is applied to the tree \n> structure afterwards?\n\nIIRC you can actually edit the tree while you are walking through it.\nYou just have to watch out for the fact that a struct tree_content\ncan be reallocated (and thus moved in memory) if the entry_capacity\nwas too small for the new entry_count when inserting a new entry.\n\ntree_content_set() handles this in its API by taking a struct\ntree_entry* rather than a struct tree_content*.  This way if the\ntree has to expand during the set and gets reallocated we can return\nthe new tree pointer to the caller through the struct tree_entry\ntree field.\n\n-- \nShawn.\n"},{"id":"132114","messageId":"81b0412b1001190754m37ed01b0nd93b318d77d88a75@mail.gmail.com","threadId":"21682","inReplyTo":"7vk4xl1nkl.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2010-01-19T15:54:26Z","receivedAt":"2010-01-19T15:54:26Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"On Fri, Nov 20, 2009 at 11:46, Junio C Hamano <gitster@pobox.com> wrote:\n> @@ -716,7 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n>                if (!strcmp(arg, \"-\"))\n>                        arg = \"@{-1}\";\n>\n> -               if (get_sha1(arg, rev)) {\n> +               if (get_sha1_mb(arg, rev)) {\n>                        if (has_dash_dash)          /* case (1) */\n>                                die(\"invalid reference: %s\", arg);\n>                        if (!patch_mode &&\n\nThis is a bit of a problem on Windows, as the arg (eventually containing\nsomething like \"master...\"), will be passed to resolve_ref below. Now, Windows,\nbeing the piece of shit it is, will lie and tell that a file\n\"refs/heads/master...\"\nexists and be same as \"refs/heads/master\". This breaks \"checkout to merge\nbase\" on Windows and t2012 in particular.\n\nBTW, why should the arg be run through resolve_ref at all if\nget_sha1(_mb) has succeeded?\nIsn't the commit already resolved by lookup_commit_reference_gently?\n"},{"id":"132125","messageId":"7vk4ve9dad.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"81b0412b1001190754m37ed01b0nd93b318d77d88a75@mail.gmail.com","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-01-19T18:10:02Z","receivedAt":"2010-01-19T18:10:02Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alex Riesen <raa.lkml@gmail.com> writes:\n\n> On Fri, Nov 20, 2009 at 11:46, Junio C Hamano <gitster@pobox.com> wrote:\n>> @@ -716,7 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n>>                if (!strcmp(arg, \"-\"))\n>>                        arg = \"@{-1}\";\n>>\n>> -               if (get_sha1(arg, rev)) {\n>> +               if (get_sha1_mb(arg, rev)) {\n>>                        if (has_dash_dash)          /* case (1) */\n>>                                die(\"invalid reference: %s\", arg);\n>>                        if (!patch_mode &&\n>\n> This is a bit of a problem on Windows, as the arg (eventually containing\n> something like \"master...\"), will be passed to resolve_ref below.\n\nI don't see \"resolve_ref below\"; do you mean this part?\n\n\t\t/* we can't end up being in (2) anymore, eat the argument */\n\t\targv++;\n\t\targc--;\n\n\t\tnew.name = arg;\n\t\tif ((new.commit = lookup_commit_reference_gently(rev, 1))) {\n\t\t\tsetup_branch_path(&new);\n\t\t\tif (resolve_ref(new.path, rev, 1, NULL))\n\t\t\t\tnew.commit = lookup_commit_reference(rev);\n\t\t\telse\n\t\t\t\tnew.path = NULL;\n\t\t\tparse_commit(new.commit);\n\t\t\tsource_tree = new.commit->tree;\n\t\t} else\n\t\t\tsource_tree = parse_tree_indirect(rev);\n\nIt is primarily to tell \"git checkout master\" and \"git checkout master^0\"\napart (the latter must detach HEAD).  IOW, it is testing if you gave the\n\"name\" of the branch, or something that yields the commit object name that\nhappens to be at the tip of the branch.  So the call to resolve_ref() is\nvery relevant.\n\n> Now, Windows,\n> being the piece of shit it is, will lie and tell that a file\n> \"refs/heads/master...\"\n> exists and be same as \"refs/heads/master\".\n\nMeh; then windows users cannot use \"git checkout A...\" syntax (they can\nstill use \"git checkout A...HEAD\", I presume).\n\nThis is not a problem specific to three-dots, but if you give the function\n\"refs/heads/master..\"  it would also get \"exists\" back, no?  You could\nteach resolve_ref() about windows (namely, the filesystem may lie about\nanything that ends with a series of dots).\n\n> This breaks \"checkout to merge base\" on Windows and t2012 in particular.\n\n> BTW, why should the arg be run through resolve_ref at all if\n> get_sha1(_mb) has succeeded?\n> Isn't the commit already resolved by lookup_commit_reference_gently?\n\nl-c-r-g makes sure rev (that is raw object name SHA-1) points at a commit\nand doesn't have anything to do with the name.\n"},{"id":"132163","messageId":"7vska15u92.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"7vk4ve9dad.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-01-20T03:29:29Z","receivedAt":"2010-01-20T03:29:29Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Alex Riesen <raa.lkml@gmail.com> writes:\n>\n>> On Fri, Nov 20, 2009 at 11:46, Junio C Hamano <gitster@pobox.com> wrote:\n>>> @@ -716,7 +719,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n>>>                if (!strcmp(arg, \"-\"))\n>>>                        arg = \"@{-1}\";\n>>>\n>>> -               if (get_sha1(arg, rev)) {\n>>> +               if (get_sha1_mb(arg, rev)) {\n>>>                        if (has_dash_dash)          /* case (1) */\n>>>                                die(\"invalid reference: %s\", arg);\n>>>                        if (!patch_mode &&\n>>\n>> This is a bit of a problem on Windows, as the arg (eventually containing\n>> something like \"master...\"), will be passed to resolve_ref below.\n>\n> I don't see \"resolve_ref below\"; do you mean this part?\n>\n> \t\t/* we can't end up being in (2) anymore, eat the argument */\n> \t\targv++;\n> \t\targc--;\n>\n> \t\tnew.name = arg;\n> \t\tif ((new.commit = lookup_commit_reference_gently(rev, 1))) {\n> \t\t\tsetup_branch_path(&new);\n> \t\t\tif (resolve_ref(new.path, rev, 1, NULL))\n> \t\t\t\tnew.commit = lookup_commit_reference(rev);\n> \t\t\telse\n> \t\t\t\tnew.path = NULL;\n> \t\t\tparse_commit(new.commit);\n> \t\t\tsource_tree = new.commit->tree;\n> \t\t} else\n> \t\t\tsource_tree = parse_tree_indirect(rev);\n>\n> It is primarily to tell \"git checkout master\" and \"git checkout master^0\"\n> apart (the latter must detach HEAD).  IOW, it is testing if you gave the\n> \"name\" of the branch, or something that yields the commit object name that\n> happens to be at the tip of the branch.  So the call to resolve_ref() is\n> very relevant.\n>\n>> Now, Windows,\n>> being the piece of shit it is, will lie and tell that a file\n>> \"refs/heads/master...\"\n>> exists and be same as \"refs/heads/master\".\n>\n> Meh; then windows users cannot use \"git checkout A...\" syntax (they can\n> still use \"git checkout A...HEAD\", I presume).\n>\n> This is not a problem specific to three-dots, but if you give the function\n> \"refs/heads/master..\"  it would also get \"exists\" back, no?  You could\n> teach resolve_ref() about windows (namely, the filesystem may lie about\n> anything that ends with a series of dots).\n>\n>> This breaks \"checkout to merge base\" on Windows and t2012 in particular.\n\nI think the attached patch would help.  If this fix is Ok with Windows\npeople (J6t CC'ed), I'd like to apply this to 'master' so that we can ship\n1.7.0-rc0 without breakage.\n\nThanks.\n\n-- >8 --\nSubject: Fix \"checkout A...\" synonym for \"checkout A...HEAD\" on Windows\n\nWhen switching to a different commit, we first see the named rev exists\nas a commit using lookup_commit_reference_gently(), and set new.path to\na string \"refs/heads/\" followed by the name the user gave us (but after\ntaking into special short-hands like @{-1} == \"previous branch\" and\n\"@{upstream}\" == \"the branch we merge with\" into account).  If the\nresulting string names an existsing ref, then we are switching to that\nbranch (and will be building new commits on top of it); otherwise we are\ndetaching HEAD at that commit.\n\nWhen the \"master...\" syntax is used as a short-hand for \"master...HEAD\",\nwe do want to detach HEAD at the merge base.  However, on Windows, when\nasked if \".git/refs/heads/master...\" exists, the filesystem happily says\n\"it does\" when \".git/refs/heads/master\" exists.\n\nWork this issue around by first calling check_ref_format(new.path) to see\nif the string can possibly be a valid ref under \"refs/heads/\", before\nasking resolve_ref().\n\nWe used to run another lookup_commit_reference(rev) even though we know it\nsucceeded and we have a good commit in new.commit already; this has been\nwith us from 782c2d6 (Build in checkout, 2008-02-07), the first version we\nhad \"git checkout\" implemented in C.  Drop it.\n\nNoticed by Alex Riesen.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin-checkout.c |    6 ++++--\n 1 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-checkout.c b/builtin-checkout.c\nindex fe7c858..ad3c01f 100644\n--- a/builtin-checkout.c\n+++ b/builtin-checkout.c\n@@ -745,8 +745,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tnew.name = arg;\n \t\tif ((new.commit = lookup_commit_reference_gently(rev, 1))) {\n \t\t\tsetup_branch_path(&new);\n-\t\t\tif (resolve_ref(new.path, rev, 1, NULL))\n-\t\t\t\tnew.commit = lookup_commit_reference(rev);\n+\n+\t\t\tif ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&\n+\t\t\t    resolve_ref(new.path, rev, 1, NULL))\n+\t\t\t\t;\n \t\t\telse\n \t\t\t\tnew.path = NULL;\n \t\t\tparse_commit(new.commit);\n"},{"id":"132169","messageId":"4B56BC22.1040702@viscovery.net","threadId":"21682","inReplyTo":"7vska15u92.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2010-01-20T08:17:38Z","receivedAt":"2010-01-20T08:17:38Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> Alex Riesen <raa.lkml@gmail.com> writes:\n>>> This breaks \"checkout to merge base\" on Windows and t2012 in particular.\n> \n> I think the attached patch would help.  If this fix is Ok with Windows\n> people (J6t CC'ed), I'd like to apply this to 'master' so that we can ship\n> 1.7.0-rc0 without breakage.\n> \n> -- >8 --\n> Subject: Fix \"checkout A...\" synonym for \"checkout A...HEAD\" on Windows\n> ...\n> Work this issue around by first calling check_ref_format(new.path) to see\n> if the string can possibly be a valid ref under \"refs/heads/\", before\n> asking resolve_ref().\n\nYour patch fixes the failure very nicely, thank you very much.\n\nBTW, calling 'git pack-refs --all' before 'git checkout master...'\nhelps as well :-P\n\n-- Hannes\n"},{"id":"132170","messageId":"7vhbqh5g45.fsf@alter.siamese.dyndns.org","threadId":"21682","inReplyTo":"4B56BC22.1040702@viscovery.net","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-01-20T08:34:50Z","receivedAt":"2010-01-20T08:34:50Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n>> Subject: Fix \"checkout A...\" synonym for \"checkout A...HEAD\" on Windows\n>> ...\n>> Work this issue around by first calling check_ref_format(new.path) to see\n>> if the string can possibly be a valid ref under \"refs/heads/\", before\n>> asking resolve_ref().\n>\n> Your patch fixes the failure very nicely, thank you very much.\n>\n> BTW, calling 'git pack-refs --all' before 'git checkout master...'\n> helps as well :-P\n\nThanks for a prompt response (given our timezone difference ;-)\n\nI've been thinking about moving that check even further down, namely at\nthe beginning of resolve_ref(), as anything that is not valid cannot\npossibly resolve correctly.\n\nWhat do people think?\n"},{"id":"132179","messageId":"81b0412b1001200206h31b61c00p218a5278996993b2@mail.gmail.com","threadId":"21682","inReplyTo":"7vska15u92.fsf@alter.siamese.dyndns.org","subject":"Re: [RFC/PATCHv8 00/10] git notes","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2010-01-20T10:06:47Z","receivedAt":"2010-01-20T10:06:47Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"On Wed, Jan 20, 2010 at 04:29, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>>> This breaks \"checkout to merge base\" on Windows and t2012 in particular.\n>\n> I think the attached patch would help.  If this fix is Ok with Windows\n\nFWIW, I confirm that it does. The test passes.\n"}]}