{"thread":{"id":"21168","subject":"[RFC/PATCHv7 04/22] Add an expensive test for git-notes","startedAt":"2009-10-09T10:21:55Z","lastAt":"2011-02-01T00:13:06Z","messageCount":33,"participants":["Johan Herland","Shawn O. Pearce","Jonathan Nieder","Sverre Rabbelier","Sam Vilain"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"124457","messageId":"1255083738-23263-1-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":null,"subject":"[RFC/PATCHv7 00/22] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:21:55Z","receivedAt":"2009-10-09T10:21:55Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Hi,\n\n(Feel free to put this on hold until v1.6.5 is released. In any case,\nI'm going to Berlin for the weekend, and don't expect to read much\nemail...)\n\nHere is the 7th iteration of the git-notes series. Changes in this\niteration are as follows:\n\n- Rebased onto current 'next'\n\n- Patch 1: Include minor leak fix\n\n- Patch 10: Rename free_commit_notes() to free_notes() (Notes are\n  no longer bound to commits only, see patch 15 for details)\n\n- Patch 12: Remove tests that are invalidated by concatenation code\n  in patch 13.\n\nOverall, I consider the 12 first patches fairly stable at this point.\nThere's also a slew of new patches, that has more of an RFC status:\n\n- Patches 13-14: Concatenation of multiple notes annotating the same\n  commit/object. This was originally suggested by mugwump many months\n  ago, and the suggestion was re-iterated by Dscho. This change has a\n  minor perfomance impact (see [1]), but I still think it's worth it.\n\n- Patch 15: Allow notes to be attached to any object (not just commits).\n  Rename get_commit_notes() to format_note() to reflect this change.\n\n- Patch 16-19: Expand notes API in preparation for querying and\n  manipulating notes from elsewhere in Git (see patch 22 for examples).\n\n- Patch 20: Add a new notes_tree struct, and use it as the first\n  parameter to all functions in the notes API. This allows API users to\n  maintain their own (multiple, concurrent) notes trees (see patch 22\n  for an example). We still have a default notes tree in notes.c as a\n  fallback (when NULL is passed as to an API function).\n\n- Patch 21: The default behaviour when there are multiple notes for a\n  given object is to concatenate them. However, some callers (see patch\n  22) want to tweak this behaviour. This patch defines a new function\n  type: combine_notes_fn, for combining two notes that reference the\n  same object. The notes API is then expanded to allow the caller to\n  specify a suitable combine_notes_fn. For convenience, three simple\n  combine_notes functions are available in the notes API:\n  - combine_notes_concatenate(): Concatenates the contents of the two\n    notes. (This is the default behaviour)\n  - combine_notes_overwrite(): Overwrite the existing note with the\n    new note.\n  - combine_notes_ignore(): Keep the existing note, and ignore the new\n    note.\n\n- Patch 22: This teaches fast-import to use the new notes API when\n  adding note objects to a commit. Since adding a note to a notes tree\n  might cause restructuring of that notes tree, the note objects must\n  be handled differently from regular blobs.\n  There are some testcases for the new behaviour in this patch, but not\n  enough. These will be added later.\n  This patch is still very much in RFC mode...\n\n\nAlthough this iteration brings the jh/notes topic towards feature-\ncompletion, there are still some things left to do before I consider\nthe git notes feature fully complete:\n\n- Builtin-ify git-notes shell script to take advantage of notes API\n\n- Garbage-collect notes whose referenced objects are unreachable\n\n- Handle note objects that are not blobs, but trees (e.g.\n  refs/notes/<topic>:<commit>/<subtopic>)\n\n- Add a simple notation for referring to an object's note (e.g.\n  \"<object>^{note}\")\n\n- Probably more that I haven't thought of yet...\n\nHowever, It might be a good idea to consider merging the early/stable\nparts of jh/notes, instead of waiting for everything to complete.\n\n\nHave fun! :)\n\n...Johan\n\n\n[1] Performance impact of the concatenation rewrite.\n\nIn order to concatenate notes correctly, the tree traversal code must be\nchanged to more proactively unpack subtree entries (so that we can safely\ndetermine whether there are multiple notes for a given key).\n\nAs before, the test case is as follows:\nLinux kernel repo with 157101 commits, 1 note per commit, organized into\nvarious fanout schemes. Hardware is Intel Core 2 Quad with 4GB RAM.\n\n\nAlgorithm / Notes tree   git log -n10 (x100)   git log --all\n\nnext / no-notes                 4.78s             63.90s\n\nbefore / no-notes               4.77s             63.61s\nbefore / no-fanout             56.59s             65.19s\n\n16tree / no-notes               4.73s             63.80s\n16tree / no-fanout             30.21s             65.11s\n16tree / 2_38                   5.53s             65.24s\n16tree / 2_2_36                 5.15s             65.12s\n\nconcat / no-notes               4.80s             64.21s\nconcat / no-fanout             30.66s             65.35s\nconcat / 2_38                   5.64s             65.87s\nconcat / 2_2_36                 5.23s             66.44s\n\nConclusion: There is a small, but measurable impact (about .1s or so in\nthe 100 x 'git log -n10' case), but I think this is small enough to be\nacceptable.\n\n\nJohan Herland (17):\n  Teach \"-m <msg>\" and \"-F <file>\" to \"git notes edit\"\n  fast-import: Add support for importing commit notes\n  t3302-notes-index-expensive: Speed up create_repo()\n  Add flags to get_commit_notes() to control the format of the note string\n  Teach notes code to free its internal data structures on request\n  Teach the notes lookup code to parse notes trees with various fanout schemes\n  Add selftests verifying that we can parse notes trees with various fanouts\n  Refactor notes code to concatenate multiple notes annotating the same object\n  Add selftests verifying concatenation of multiple notes for the same commit\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\nJohannes Schindelin (5):\n  Introduce commit notes\n  Add a script to edit/inspect notes\n  Speed up git notes lookup\n  Add an expensive test for git-notes\n  Add '%N'-format for pretty-printing commit notes\n\n .gitignore                        |    1 +\n Documentation/config.txt          |   13 +\n Documentation/git-fast-import.txt |   45 +++-\n Documentation/git-notes.txt       |   60 ++++\n Documentation/pretty-formats.txt  |    1 +\n Makefile                          |    3 +\n cache.h                           |    4 +\n command-list.txt                  |    1 +\n commit.c                          |    1 +\n config.c                          |    5 +\n environment.c                     |    1 +\n fast-import.c                     |  176 +++++++++++-\n git-notes.sh                      |  121 ++++++++\n notes.c                           |  579 +++++++++++++++++++++++++++++++++++++\n notes.h                           |  113 +++++++\n pretty.c                          |   10 +\n t/t3301-notes.sh                  |  150 ++++++++++\n t/t3302-notes-index-expensive.sh  |  118 ++++++++\n t/t3303-notes-subtrees.sh         |  188 ++++++++++++\n t/t9300-fast-import.sh            |  296 +++++++++++++++++++\n 20 files changed, 1875 insertions(+), 11 deletions(-)\n create mode 100644 Documentation/git-notes.txt\n create mode 100755 git-notes.sh\n create mode 100644 notes.c\n create mode 100644 notes.h\n create mode 100755 t/t3301-notes.sh\n create mode 100755 t/t3302-notes-index-expensive.sh\n create mode 100755 t/t3303-notes-subtrees.sh\n"},{"id":"124463","messageId":"1255083738-23263-2-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 00/22] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:21:56Z","receivedAt":"2009-10-09T10:21:56Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Hi,\n\nHere is the 7th iteration of the git-notes series. Changes in this\niteration are as follows:\n\n- Rebased onto current 'next'\n\n- Patch 1: Include minor leak fix\n\n- Patch 10: Rename free_commit_notes() to free_notes() (Notes are\n  no longer bound to only commits, see patch 15 for details)\n\n- Patch 12: Remove tests that are invalidated by concatenation code\n  in patch 13.\n\nThere's also a slew of new patches:\n\n- Patches 13-14: Concatenation of multiple notes annotating the same\n  commit/object. This was originally suggested by mugwump many months\n  ago, and the suggestion was re-iterated by Dscho. This change has a\n  minor perfomance impact (see [1]), but I still think it's worth it.\n\n- Patch 15: Allow notes to be attached to any object (not just commits).\n  Rename get_commit_notes() to format_note() to reflect this change.\n\n- Patch 16-19: Expand notes API in preparation for querying and manipulating notes from other parts of Git.\n\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\nHave fun! :)\n\n...Johan\n\n\n[1] Performance impact of the concatenation rewrite.\n\nIn order to concatenate notes correctly, the tree traversal code must be\nchanged to more proactively unpack subtree entries (so that we can safely\ndetermine whether there are multiple notes for a given key).\n\nAs before, the test case is as follows:\nLinux kernel repo with 157101 commits, 1 note per commit, organized into\nvarious fanout schemes. Hardware is Intel Core 2 Quad with 4GB RAM.\n\n\nAlgorithm / Notes tree   git log -n10 (x100)   git log --all\n\nnext / no-notes                 4.78s             63.90s\n\nbefore / no-notes               4.77s             63.61s\nbefore / no-fanout             56.59s             65.19s\n\n16tree / no-notes               4.73s             63.80s\n16tree / no-fanout             30.21s             65.11s\n16tree / 2_38                   5.53s             65.24s\n16tree / 2_2_36                 5.15s             65.12s\n\nconcat / no-notes               4.80s             64.21s\nconcat / no-fanout             30.66s             65.35s\nconcat / 2_38                   5.64s             65.87s\nconcat / 2_2_36                 5.23s             66.44s\n\nConclusion: There is a measurable impact (about .1s or so in the 100 x\n'git log -n10' case), but I think this is low enough to be acceptable.\n\n\nJohan Herland (17):\n  Teach \"-m <msg>\" and \"-F <file>\" to \"git notes edit\"\n  fast-import: Add support for importing commit notes\n  t3302-notes-index-expensive: Speed up create_repo()\n  Add flags to get_commit_notes() to control the format of the note string\n  Teach notes code to free its internal data structures on request.\n  Teach the notes lookup code to parse notes trees with various fanout schemes\n  Add selftests verifying that we can parse notes trees with various fanouts\n  Refactor notes code to concatenate multiple notes annotating the same object\n  Add selftests verifying that multiple notes for the same commits are concatenated correctly\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\nJohannes Schindelin (5):\n  Introduce commit notes\n  Add a script to edit/inspect notes\n  Speed up git notes lookup\n  Add an expensive test for git-notes\n  Add '%N'-format for pretty-printing commit notes\n\n .gitignore                        |    1 +\n Documentation/config.txt          |   13 +\n Documentation/git-fast-import.txt |   45 +++-\n Documentation/git-notes.txt       |   60 ++++\n Documentation/pretty-formats.txt  |    1 +\n Makefile                          |    3 +\n cache.h                           |    4 +\n command-list.txt                  |    1 +\n commit.c                          |    1 +\n config.c                          |    5 +\n environment.c                     |    1 +\n fast-import.c                     |  176 +++++++++++-\n git-notes.sh                      |  121 ++++++++\n notes.c                           |  579 +++++++++++++++++++++++++++++++++++++\n notes.h                           |  121 ++++++++\n pretty.c                          |   10 +\n t/t3301-notes.sh                  |  150 ++++++++++\n t/t3302-notes-index-expensive.sh  |  118 ++++++++\n t/t3303-notes-subtrees.sh         |  188 ++++++++++++\n t/t9300-fast-import.sh            |  296 +++++++++++++++++++\n 20 files changed, 1883 insertions(+), 11 deletions(-)\n create mode 100644 Documentation/git-notes.txt\n create mode 100755 git-notes.sh\n create mode 100644 notes.c\n create mode 100644 notes.h\n create mode 100755 t/t3301-notes.sh\n create mode 100755 t/t3302-notes-index-expensive.sh\n create mode 100755 t/t3303-notes-subtrees.sh\n"},{"id":"124455","messageId":"1255083738-23263-3-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 01/22] Introduce commit notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:21:57Z","receivedAt":"2009-10-09T10:21:57Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nCommit notes are blobs which are shown together with the commit\nmessage.  These blobs are taken from the notes ref, which you can\nconfigure by the config variable core.notesRef, which in turn can\nbe overridden by the environment variable GIT_NOTES_REF.\n\nThe notes ref is a branch which contains \"files\" whose names are\nthe names of the corresponding commits (i.e. the SHA-1).\n\nThe rationale for putting this information into a ref is this: we\nwant to be able to fetch and possibly union-merge the notes,\nmaybe even look at the date when a note was introduced, and we\nwant to store them efficiently together with the other objects.\n\nThis patch has been improved by the following contributions:\n- Thomas Rast: fix core.notesRef documentation\n- Tor Arne Vestbø: fix printing of multi-line notes\n- Alex Riesen: Using char array instead of char pointer costs less BSS\n- Johan Herland: Plug leak when msg is good, but msglen or type causes return\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\nSigned-off-by: Tor Arne Vestbø <tavestbo@trolltech.com>\nSigned-off-by: Johan Herland <johan@herland.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n\nget_commit_notes(): Plug memory leak when 'if' triggers, but not because of read_sha1_file() failure\n---\n Documentation/config.txt |   13 ++++++++\n Makefile                 |    2 +\n cache.h                  |    4 ++\n commit.c                 |    1 +\n config.c                 |    5 +++\n environment.c            |    1 +\n notes.c                  |   70 ++++++++++++++++++++++++++++++++++++++++++++++\n notes.h                  |    7 ++++\n pretty.c                 |    5 +++\n 9 files changed, 108 insertions(+), 0 deletions(-)\n create mode 100644 notes.c\n create mode 100644 notes.h\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex cc156b8..32b0cdf 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -458,6 +458,19 @@ On some file system/operating system combinations, this is unreliable.\n Set this config setting to 'rename' there; However, This will remove the\n check that makes sure that existing object files will not get overwritten.\n \n+core.notesRef::\n+\tWhen showing commit messages, also show notes which are stored in\n+\tthe given ref.  This ref is expected to contain files named\n+\tafter the full SHA-1 of the commit they annotate.\n++\n+If such a file exists in the given ref, the referenced blob is read, and\n+appended to the commit message, separated by a \"Notes:\" line.  If the\n+given ref itself does not exist, it is not an error, but means that no\n+notes should be printed.\n++\n+This setting defaults to \"refs/notes/commits\", and can be overridden by\n+the `GIT_NOTES_REF` environment variable.\n+\n add.ignore-errors::\n \tTells 'git-add' to continue adding files when some files cannot be\n \tadded due to indexing errors. Equivalent to the '--ignore-errors'\ndiff --git a/Makefile b/Makefile\nindex 8925b1d..9e414db 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -429,6 +429,7 @@ LIB_H += ll-merge.h\n LIB_H += log-tree.h\n LIB_H += mailmap.h\n LIB_H += merge-recursive.h\n+LIB_H += notes.h\n LIB_H += object.h\n LIB_H += pack.h\n LIB_H += pack-refs.h\n@@ -513,6 +514,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += merge-file.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += name-hash.o\n+LIB_OBJS += notes.o\n LIB_OBJS += object.o\n LIB_OBJS += pack-check.o\n LIB_OBJS += pack-refs.o\ndiff --git a/cache.h b/cache.h\nindex 96840c7..0343e8e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -372,6 +372,8 @@ static inline enum object_type object_type(unsigned int mode)\n #define GITATTRIBUTES_FILE \".gitattributes\"\n #define INFOATTRIBUTES_FILE \"info/attributes\"\n #define ATTRIBUTE_MACRO_PREFIX \"[attr]\"\n+#define GIT_NOTES_REF_ENVIRONMENT \"GIT_NOTES_REF\"\n+#define GIT_NOTES_DEFAULT_REF \"refs/notes/commits\"\n \n extern int is_bare_repository_cfg;\n extern int is_bare_repository(void);\n@@ -567,6 +569,8 @@ enum object_creation_mode {\n \n extern enum object_creation_mode object_creation_mode;\n \n+extern char *notes_ref_name;\n+\n extern int grafts_replace_parents;\n \n #define GIT_REPO_VERSION 0\ndiff --git a/commit.c b/commit.c\nindex fedbd5e..5ade8ed 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -5,6 +5,7 @@\n #include \"utf8.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n+#include \"notes.h\"\n \n int save_commit_buffer = 1;\n \ndiff --git a/config.c b/config.c\nindex c644061..51f2208 100644\n--- a/config.c\n+++ b/config.c\n@@ -467,6 +467,11 @@ static int git_default_core_config(const char *var, const char *value)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(var, \"core.notesref\")) {\n+\t\tnotes_ref_name = xstrdup(value);\n+\t\treturn 0;\n+\t}\n+\n \tif (!strcmp(var, \"core.pager\"))\n \t\treturn git_config_string(&pager_program, var, value);\n \ndiff --git a/environment.c b/environment.c\nindex 5de6837..571ab56 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,6 +49,7 @@ enum push_default_type push_default = PUSH_DEFAULT_MATCHING;\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n+char *notes_ref_name;\n int grafts_replace_parents = 1;\n \n /* Parallel index stat data preload? */\ndiff --git a/notes.c b/notes.c\nnew file mode 100644\nindex 0000000..66379ff\n--- /dev/null\n+++ b/notes.c\n@@ -0,0 +1,70 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"notes.h\"\n+#include \"refs.h\"\n+#include \"utf8.h\"\n+#include \"strbuf.h\"\n+\n+static int initialized;\n+\n+void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n+\t\tconst char *output_encoding)\n+{\n+\tstatic const char utf8[] = \"utf-8\";\n+\tstruct strbuf name = STRBUF_INIT;\n+\tunsigned char sha1[20];\n+\tchar *msg, *msg_p;\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\tif (notes_ref_name && read_ref(notes_ref_name, sha1))\n+\t\t\tnotes_ref_name = NULL;\n+\t\tinitialized = 1;\n+\t}\n+\n+\tif (!notes_ref_name)\n+\t\treturn;\n+\n+\tstrbuf_addf(&name, \"%s:%s\", notes_ref_name,\n+\t\t\tsha1_to_hex(commit->object.sha1));\n+\tif (get_sha1(name.buf, sha1))\n+\t\treturn;\n+\n+\tif (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||\n+\t\t\ttype != OBJ_BLOB) {\n+\t\tfree(msg);\n+\t\treturn;\n+\t}\n+\n+\tif (output_encoding && *output_encoding &&\n+\t\t\tstrcmp(utf8, output_encoding)) {\n+\t\tchar *reencoded = reencode_string(msg, output_encoding, utf8);\n+\t\tif (reencoded) {\n+\t\t\tfree(msg);\n+\t\t\tmsg = reencoded;\n+\t\t\tmsglen = strlen(msg);\n+\t\t}\n+\t}\n+\n+\t/* we will end the annotation by a newline anyway */\n+\tif (msglen && msg[msglen - 1] == '\\n')\n+\t\tmsglen--;\n+\n+\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n+\n+\tfor (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {\n+\t\tlinelen = strchrnul(msg_p, '\\n') - msg_p;\n+\n+\t\tstrbuf_addstr(sb, \"    \");\n+\t\tstrbuf_add(sb, msg_p, linelen);\n+\t\tstrbuf_addch(sb, '\\n');\n+\t}\n+\n+\tfree(msg);\n+}\ndiff --git a/notes.h b/notes.h\nnew file mode 100644\nindex 0000000..79d21b6\n--- /dev/null\n+++ b/notes.h\n@@ -0,0 +1,7 @@\n+#ifndef NOTES_H\n+#define NOTES_H\n+\n+void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n+\t\tconst char *output_encoding);\n+\n+#endif\ndiff --git a/pretty.c b/pretty.c\nindex f5983f8..e25db81 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -6,6 +6,7 @@\n #include \"string-list.h\"\n #include \"mailmap.h\"\n #include \"log-tree.h\"\n+#include \"notes.h\"\n #include \"color.h\"\n \n static char *user_format;\n@@ -975,5 +976,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \t */\n \tif (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)\n \t\tstrbuf_addch(sb, '\\n');\n+\n+\tif (fmt != CMIT_FMT_ONELINE)\n+\t\tget_commit_notes(commit, sb, encoding);\n+\n \tfree(reencoded);\n }\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124465","messageId":"1255083738-23263-4-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 02/22] Add a script to edit/inspect notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:21:58Z","receivedAt":"2009-10-09T10:21:58Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nThe script 'git notes' allows you to edit and show commit notes, by\ncalling either\n\n\tgit notes show <commit>\n\nor\n\n\tgit notes edit <commit>\n\nThis patch has been improved by the following contributions:\n- Tor Arne Vestbø: fix printing of multi-line notes\n- Michael J Gruber: test and handle empty notes gracefully\n- Thomas Rast:\n  - only clean up message file when editing\n  - use GIT_EDITOR and core.editor over VISUAL/EDITOR\n  - t3301: fix confusing quoting in test for valid notes ref\n  - t3301: use test_must_fail instead of !\n  - refuse to edit notes outside refs/notes/\n- Junio C Hamano: tests: fix \"export var=val\"\n- Christian Couder: documentation: fix 'linkgit' macro in \"git-notes.txt\"\n- Johan Herland: minor cleanup and bugfixing in git-notes.sh (v2)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Tor Arne Vestbø <tavestbo@trolltech.com>\nSigned-off-by: Michael J Gruber <git@drmicha.warpmail.net>\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Johan Herland <johan@herland.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore                  |    1 +\n Documentation/git-notes.txt |   46 +++++++++++++++++\n Makefile                    |    1 +\n command-list.txt            |    1 +\n git-notes.sh                |   73 +++++++++++++++++++++++++++\n t/t3301-notes.sh            |  114 +++++++++++++++++++++++++++++++++++++++++++\n 6 files changed, 236 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-notes.txt\n create mode 100755 git-notes.sh\n create mode 100755 t/t3301-notes.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 51a37b1..cbafa64 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -86,6 +86,7 @@ git-mktag\n git-mktree\n git-name-rev\n git-mv\n+git-notes\n git-pack-redundant\n git-pack-objects\n git-pack-refs\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nnew file mode 100644\nindex 0000000..7136016\n--- /dev/null\n+++ b/Documentation/git-notes.txt\n@@ -0,0 +1,46 @@\n+git-notes(1)\n+============\n+\n+NAME\n+----\n+git-notes - Add/inspect commit notes\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git-notes' (edit | show) [commit]\n+\n+DESCRIPTION\n+-----------\n+This command allows you to add notes to commit messages, without\n+changing the commit.  To discern these notes from the message stored\n+in the commit object, the notes are indented like the message, after\n+an unindented line saying \"Notes:\".\n+\n+To disable commit notes, you have to set the config variable\n+core.notesRef to the empty string.  Alternatively, you can set it\n+to a different ref, something like \"refs/notes/bugzilla\".  This setting\n+can be overridden by the environment variable \"GIT_NOTES_REF\".\n+\n+\n+SUBCOMMANDS\n+-----------\n+\n+edit::\n+\tEdit the notes for a given commit (defaults to HEAD).\n+\n+show::\n+\tShow the notes for a given commit (defaults to HEAD).\n+\n+\n+Author\n+------\n+Written by Johannes Schindelin <johannes.schindelin@gmx.de>\n+\n+Documentation\n+-------------\n+Documentation by Johannes Schindelin\n+\n+GIT\n+---\n+Part of the linkgit:git[7] suite\ndiff --git a/Makefile b/Makefile\nindex 9e414db..dadd814 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -318,6 +318,7 @@ SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-mergetool--lib.sh\n+SCRIPT_SH += git-notes.sh\n SCRIPT_SH += git-parse-remote.sh\n SCRIPT_SH += git-pull.sh\n SCRIPT_SH += git-quiltimport.sh\ndiff --git a/command-list.txt b/command-list.txt\nindex fb03a2e..4296941 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -74,6 +74,7 @@ git-mktag                               plumbingmanipulators\n git-mktree                              plumbingmanipulators\n git-mv                                  mainporcelain common\n git-name-rev                            plumbinginterrogators\n+git-notes                               mainporcelain\n git-pack-objects                        plumbingmanipulators\n git-pack-redundant                      plumbinginterrogators\n git-pack-refs                           ancillarymanipulators\ndiff --git a/git-notes.sh b/git-notes.sh\nnew file mode 100755\nindex 0000000..f06c254\n--- /dev/null\n+++ b/git-notes.sh\n@@ -0,0 +1,73 @@\n+#!/bin/sh\n+\n+USAGE=\"(edit | show) [commit]\"\n+. git-sh-setup\n+\n+test -n \"$3\" && usage\n+\n+test -z \"$1\" && usage\n+ACTION=\"$1\"; shift\n+\n+test -z \"$GIT_NOTES_REF\" && GIT_NOTES_REF=\"$(git config core.notesref)\"\n+test -z \"$GIT_NOTES_REF\" && GIT_NOTES_REF=\"refs/notes/commits\"\n+\n+COMMIT=$(git rev-parse --verify --default HEAD \"$@\") ||\n+die \"Invalid commit: $@\"\n+\n+case \"$ACTION\" in\n+edit)\n+\tif [ \"${GIT_NOTES_REF#refs/notes/}\" = \"$GIT_NOTES_REF\" ]; then\n+\t\tdie \"Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)\"\n+\tfi\n+\n+\tMSG_FILE=\"$GIT_DIR/new-notes-$COMMIT\"\n+\tGIT_INDEX_FILE=\"$MSG_FILE.idx\"\n+\texport GIT_INDEX_FILE\n+\n+\ttrap '\n+\t\ttest -f \"$MSG_FILE\" && rm \"$MSG_FILE\"\n+\t\ttest -f \"$GIT_INDEX_FILE\" && rm \"$GIT_INDEX_FILE\"\n+\t' 0\n+\n+\tGIT_NOTES_REF= git log -1 $COMMIT | sed \"s/^/#/\" > \"$MSG_FILE\"\n+\n+\tCURRENT_HEAD=$(git show-ref \"$GIT_NOTES_REF\" | cut -f 1 -d ' ')\n+\tif [ -z \"$CURRENT_HEAD\" ]; then\n+\t\tPARENT=\n+\telse\n+\t\tPARENT=\"-p $CURRENT_HEAD\"\n+\t\tgit read-tree \"$GIT_NOTES_REF\" || die \"Could not read index\"\n+\t\tgit cat-file blob :$COMMIT >> \"$MSG_FILE\" 2> /dev/null\n+\tfi\n+\n+\tcore_editor=\"$(git config core.editor)\"\n+\t${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} \"$MSG_FILE\"\n+\n+\tgrep -v ^# < \"$MSG_FILE\" | git stripspace > \"$MSG_FILE\".processed\n+\tmv \"$MSG_FILE\".processed \"$MSG_FILE\"\n+\tif [ -s \"$MSG_FILE\" ]; then\n+\t\tBLOB=$(git hash-object -w \"$MSG_FILE\") ||\n+\t\t\tdie \"Could not write into object database\"\n+\t\tgit update-index --add --cacheinfo 0644 $BLOB $COMMIT ||\n+\t\t\tdie \"Could not write index\"\n+\telse\n+\t\ttest -z \"$CURRENT_HEAD\" &&\n+\t\t\tdie \"Will not initialise with empty tree\"\n+\t\tgit update-index --force-remove $COMMIT ||\n+\t\t\tdie \"Could not update index\"\n+\tfi\n+\n+\tTREE=$(git write-tree) || die \"Could not write tree\"\n+\tNEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) ||\n+\t\tdie \"Could not annotate\"\n+\tgit update-ref -m \"Annotate $COMMIT\" \\\n+\t\t\"$GIT_NOTES_REF\" $NEW_HEAD $CURRENT_HEAD\n+;;\n+show)\n+\tgit rev-parse -q --verify \"$GIT_NOTES_REF\":$COMMIT > /dev/null ||\n+\t\tdie \"No note for commit $COMMIT.\"\n+\tgit show \"$GIT_NOTES_REF\":$COMMIT\n+;;\n+*)\n+\tusage\n+esac\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nnew file mode 100755\nindex 0000000..73e53be\n--- /dev/null\n+++ b/t/t3301-notes.sh\n@@ -0,0 +1,114 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Johannes E. Schindelin\n+#\n+\n+test_description='Test commit notes'\n+\n+. ./test-lib.sh\n+\n+cat > fake_editor.sh << \\EOF\n+echo \"$MSG\" > \"$1\"\n+echo \"$MSG\" >& 2\n+EOF\n+chmod a+x fake_editor.sh\n+VISUAL=./fake_editor.sh\n+export VISUAL\n+\n+test_expect_success 'cannot annotate non-existing HEAD' '\n+\t(MSG=3 && export MSG && test_must_fail git notes edit)\n+'\n+\n+test_expect_success setup '\n+\t: > a1 &&\n+\tgit add a1 &&\n+\ttest_tick &&\n+\tgit commit -m 1st &&\n+\t: > a2 &&\n+\tgit add a2 &&\n+\ttest_tick &&\n+\tgit commit -m 2nd\n+'\n+\n+test_expect_success 'need valid notes ref' '\n+\t(MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&\n+\t test_must_fail git notes edit) &&\n+\t(MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF &&\n+\t test_must_fail git notes show)\n+'\n+\n+test_expect_success 'refusing to edit in refs/heads/' '\n+\t(MSG=1 GIT_NOTES_REF=refs/heads/bogus &&\n+\t export MSG GIT_NOTES_REF &&\n+\t test_must_fail git notes edit)\n+'\n+\n+test_expect_success 'refusing to edit in refs/remotes/' '\n+\t(MSG=1 GIT_NOTES_REF=refs/remotes/bogus &&\n+\t export MSG GIT_NOTES_REF &&\n+\t test_must_fail git notes edit)\n+'\n+\n+# 1 indicates caught gracefully by die, 128 means git-show barked\n+test_expect_success 'handle empty notes gracefully' '\n+\tgit notes show ; test 1 = $?\n+'\n+\n+test_expect_success 'create notes' '\n+\tgit config core.notesRef refs/notes/commits &&\n+\tMSG=b1 git notes edit &&\n+\ttest ! -f .git/new-notes &&\n+\ttest 1 = $(git ls-tree refs/notes/commits | wc -l) &&\n+\ttest b1 = $(git notes show) &&\n+\tgit show HEAD^ &&\n+\ttest_must_fail git notes show HEAD^\n+'\n+\n+cat > expect << EOF\n+commit 268048bfb8a1fb38e703baceb8ab235421bf80c5\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:14:13 2005 -0700\n+\n+    2nd\n+\n+Notes:\n+    b1\n+EOF\n+\n+test_expect_success 'show notes' '\n+\t! (git cat-file commit HEAD | grep b1) &&\n+\tgit log -1 > output &&\n+\ttest_cmp expect output\n+'\n+test_expect_success 'create multi-line notes (setup)' '\n+\t: > a3 &&\n+\tgit add a3 &&\n+\ttest_tick &&\n+\tgit commit -m 3rd &&\n+\tMSG=\"b3\n+c3c3c3c3\n+d3d3d3\" git notes edit\n+'\n+\n+cat > expect-multiline << EOF\n+commit 1584215f1d29c65e99c6c6848626553fdd07fd75\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:15:13 2005 -0700\n+\n+    3rd\n+\n+Notes:\n+    b3\n+    c3c3c3c3\n+    d3d3d3\n+EOF\n+\n+printf \"\\n\" >> expect-multiline\n+cat expect >> expect-multiline\n+\n+test_expect_success 'show multi-line notes' '\n+\tgit log -2 > output &&\n+\ttest_cmp expect-multiline output\n+'\n+\n+test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124460","messageId":"1255083738-23263-5-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 03/22] Speed up git notes lookup","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:21:59Z","receivedAt":"2009-10-09T10:21:59Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nTo avoid looking up each and every commit in the notes ref's tree\nobject, which is very expensive, speed things up by slurping the tree\nobject's contents into a hash_map.\n\nThe idea for the hashmap singleton is from David Reiss, initial\nbenchmarking by Jeff King.\n\nNote: the implementation allows for arbitrary entries in the notes\ntree object, ignoring those that do not reference a valid object.  This\nallows you to annotate arbitrary branches, or objects.\n\nThis patch has been improved by the following contributions:\n- Junio C Hamano: fixed an obvious error in initialize_hash_map()\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n notes.c |  112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------\n 1 files changed, 102 insertions(+), 10 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 66379ff..2b66723 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -4,15 +4,112 @@\n #include \"refs.h\"\n #include \"utf8.h\"\n #include \"strbuf.h\"\n+#include \"tree-walk.h\"\n+\n+struct entry {\n+\tunsigned char commit_sha1[20];\n+\tunsigned char notes_sha1[20];\n+};\n+\n+struct hash_map {\n+\tstruct entry *entries;\n+\toff_t count, size;\n+};\n \n static int initialized;\n+static struct hash_map hash_map;\n+\n+static int hash_index(struct hash_map *map, const unsigned char *sha1)\n+{\n+\tint i = ((*(unsigned int *)sha1) % map->size);\n+\n+\tfor (;;) {\n+\t\tunsigned char *current = map->entries[i].commit_sha1;\n+\n+\t\tif (!hashcmp(sha1, current))\n+\t\t\treturn i;\n+\n+\t\tif (is_null_sha1(current))\n+\t\t\treturn -1 - i;\n+\n+\t\tif (++i == map->size)\n+\t\t\ti = 0;\n+\t}\n+}\n+\n+static void add_entry(const unsigned char *commit_sha1,\n+\t\tconst unsigned char *notes_sha1)\n+{\n+\tint index;\n+\n+\tif (hash_map.count + 1 > hash_map.size >> 1) {\n+\t\tint i, old_size = hash_map.size;\n+\t\tstruct entry *old = hash_map.entries;\n+\n+\t\thash_map.size = old_size ? old_size << 1 : 64;\n+\t\thash_map.entries = (struct entry *)\n+\t\t\txcalloc(sizeof(struct entry), hash_map.size);\n+\n+\t\tfor (i = 0; i < old_size; i++)\n+\t\t\tif (!is_null_sha1(old[i].commit_sha1)) {\n+\t\t\t\tindex = -1 - hash_index(&hash_map,\n+\t\t\t\t\t\told[i].commit_sha1);\n+\t\t\t\tmemcpy(hash_map.entries + index, old + i,\n+\t\t\t\t\tsizeof(struct entry));\n+\t\t\t}\n+\t\tfree(old);\n+\t}\n+\n+\tindex = hash_index(&hash_map, commit_sha1);\n+\tif (index < 0) {\n+\t\tindex = -1 - index;\n+\t\thash_map.count++;\n+\t}\n+\n+\thashcpy(hash_map.entries[index].commit_sha1, commit_sha1);\n+\thashcpy(hash_map.entries[index].notes_sha1, notes_sha1);\n+}\n+\n+static void initialize_hash_map(const char *notes_ref_name)\n+{\n+\tunsigned char sha1[20], commit_sha1[20];\n+\tunsigned mode;\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tvoid *buf;\n+\n+\tif (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||\n+\t    get_tree_entry(commit_sha1, \"\", sha1, &mode))\n+\t\treturn;\n+\n+\tbuf = fill_tree_descriptor(&desc, sha1);\n+\tif (!buf)\n+\t\tdie(\"Could not read %s for notes-index\", sha1_to_hex(sha1));\n+\n+\twhile (tree_entry(&desc, &entry))\n+\t\tif (!get_sha1(entry.path, commit_sha1))\n+\t\t\tadd_entry(commit_sha1, entry.sha1);\n+\tfree(buf);\n+}\n+\n+static unsigned char *lookup_notes(const unsigned char *commit_sha1)\n+{\n+\tint index;\n+\n+\tif (!hash_map.size)\n+\t\treturn NULL;\n+\n+\tindex = hash_index(&hash_map, commit_sha1);\n+\tif (index < 0)\n+\t\treturn NULL;\n+\treturn hash_map.entries[index].notes_sha1;\n+}\n \n void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n \t\tconst char *output_encoding)\n {\n \tstatic const char utf8[] = \"utf-8\";\n-\tstruct strbuf name = STRBUF_INIT;\n-\tunsigned char sha1[20];\n+\tunsigned char *sha1;\n \tchar *msg, *msg_p;\n \tunsigned long linelen, msglen;\n \tenum object_type type;\n@@ -23,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,\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\tif (notes_ref_name && read_ref(notes_ref_name, sha1))\n-\t\t\tnotes_ref_name = NULL;\n+\t\tinitialize_hash_map(notes_ref_name);\n \t\tinitialized = 1;\n \t}\n \n-\tif (!notes_ref_name)\n-\t\treturn;\n-\n-\tstrbuf_addf(&name, \"%s:%s\", notes_ref_name,\n-\t\t\tsha1_to_hex(commit->object.sha1));\n-\tif (get_sha1(name.buf, sha1))\n+\tsha1 = lookup_notes(commit->object.sha1);\n+\tif (!sha1)\n \t\treturn;\n \n \tif (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen ||\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124454","messageId":"1255083738-23263-6-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 04/22] Add an expensive test for git-notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:00Z","receivedAt":"2009-10-09T10:22:00Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\ngit-notes have the potential of being pretty expensive, so test with\na lot of commits.  A lot.  So to make things cheaper, you have to\nopt-in explicitely, by setting the environment variable\nGIT_NOTES_TIMING_TESTS.\n\nThis patch has been improved by the following contributions:\n- Junio C Hamano: tests: fix \"export var=val\"\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t3302-notes-index-expensive.sh |   98 ++++++++++++++++++++++++++++++++++++++\n 1 files changed, 98 insertions(+), 0 deletions(-)\n create mode 100755 t/t3302-notes-index-expensive.sh\n\ndiff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh\nnew file mode 100755\nindex 0000000..0ef3e95\n--- /dev/null\n+++ b/t/t3302-notes-index-expensive.sh\n@@ -0,0 +1,98 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Johannes E. Schindelin\n+#\n+\n+test_description='Test commit notes index (expensive!)'\n+\n+. ./test-lib.sh\n+\n+test -z \"$GIT_NOTES_TIMING_TESTS\" && {\n+\tsay Skipping timing tests\n+\ttest_done\n+\texit\n+}\n+\n+create_repo () {\n+\tnumber_of_commits=$1\n+\tnr=0\n+\tparent=\n+\ttest -d .git || {\n+\tgit init &&\n+\ttree=$(git write-tree) &&\n+\twhile [ $nr -lt $number_of_commits ]; do\n+\t\ttest_tick &&\n+\t\tcommit=$(echo $nr | git commit-tree $tree $parent) ||\n+\t\t\treturn\n+\t\tparent=\"-p $commit\"\n+\t\tnr=$(($nr+1))\n+\tdone &&\n+\tgit update-ref refs/heads/master $commit &&\n+\t{\n+\t\tGIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE;\n+\t\tgit rev-list HEAD | cat -n | sed \"s/^[ \t][ \t]*/ /g\" |\n+\t\twhile read nr sha1; do\n+\t\t\tblob=$(echo note $nr | git hash-object -w --stdin) &&\n+\t\t\techo $sha1 | sed \"s/^/0644 $blob 0\t/\"\n+\t\tdone | git update-index --index-info &&\n+\t\ttree=$(git write-tree) &&\n+\t\ttest_tick &&\n+\t\tcommit=$(echo notes | git commit-tree $tree) &&\n+\t\tgit update-ref refs/notes/commits $commit\n+\t} &&\n+\tgit config core.notesRef refs/notes/commits\n+\t}\n+}\n+\n+test_notes () {\n+\tcount=$1 &&\n+\tgit config core.notesRef refs/notes/commits &&\n+\tgit log | grep \"^    \" > output &&\n+\ti=1 &&\n+\twhile [ $i -le $count ]; do\n+\t\techo \"    $(($count-$i))\" &&\n+\t\techo \"    note $i\" &&\n+\t\ti=$(($i+1));\n+\tdone > expect &&\n+\tgit diff expect output\n+}\n+\n+cat > time_notes << \\EOF\n+\tmode=$1\n+\ti=1\n+\twhile [ $i -lt $2 ]; do\n+\t\tcase $1 in\n+\t\tno-notes)\n+\t\t\tGIT_NOTES_REF=non-existing; export GIT_NOTES_REF\n+\t\t;;\n+\t\tnotes)\n+\t\t\tunset GIT_NOTES_REF\n+\t\t;;\n+\t\tesac\n+\t\tgit log >/dev/null\n+\t\ti=$(($i+1))\n+\tdone\n+EOF\n+\n+time_notes () {\n+\tfor mode in no-notes notes\n+\tdo\n+\t\techo $mode\n+\t\t/usr/bin/time sh ../time_notes $mode $1\n+\tdone\n+}\n+\n+for count in 10 100 1000 10000; do\n+\n+\tmkdir $count\n+\t(cd $count;\n+\n+\ttest_expect_success \"setup $count\" \"create_repo $count\"\n+\n+\ttest_expect_success 'notes work' \"test_notes $count\"\n+\n+\ttest_expect_success 'notes timing' \"time_notes 100\"\n+\t)\n+done\n+\n+test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124471","messageId":"1255083738-23263-7-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 05/22] Teach \"-m <msg>\" and \"-F <file>\" to \"git notes edit\"","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:01Z","receivedAt":"2009-10-09T10:22:01Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The \"-m\" and \"-F\" options are already the established method\n(in both git-commit and git-tag) to specify a commit/tag message\nwithout invoking the editor. This patch teaches \"git notes edit\"\nto respect the same options for specifying a notes message without\ninvoking the editor.\n\nMultiple \"-m\" and/or \"-F\" options are concatenated as separate\nparagraphs.\n\nThe patch also updates the \"git notes\" documentation and adds\nselftests for the new functionality. Unfortunately, the added\nselftests include a couple of lines with trailing whitespace\n(without these the test will fail). This may cause git to warn\nabout \"whitespace errors\".\n\nThis patch has been improved by the following contributions:\n- Thomas Rast: fix trailing whitespace in t3301\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/git-notes.txt |   16 ++++++++++-\n git-notes.sh                |   64 +++++++++++++++++++++++++++++++++++++-----\n t/t3301-notes.sh            |   36 ++++++++++++++++++++++++\n 3 files changed, 107 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt\nindex 7136016..94cceb1 100644\n--- a/Documentation/git-notes.txt\n+++ b/Documentation/git-notes.txt\n@@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes\n SYNOPSIS\n --------\n [verse]\n-'git-notes' (edit | show) [commit]\n+'git-notes' (edit [-F <file> | -m <msg>] | show) [commit]\n \n DESCRIPTION\n -----------\n@@ -33,6 +33,20 @@ show::\n \tShow the notes for a given commit (defaults to HEAD).\n \n \n+OPTIONS\n+-------\n+-m <msg>::\n+\tUse the given note message (instead of prompting).\n+\tIf multiple `-m` (or `-F`) options are given, their\n+\tvalues are concatenated as separate paragraphs.\n+\n+-F <file>::\n+\tTake the note message from the given file.  Use '-' to\n+\tread the note message from the standard input.\n+\tIf multiple `-F` (or `-m`) options are given, their\n+\tvalues are concatenated as separate paragraphs.\n+\n+\n Author\n ------\n Written by Johannes Schindelin <johannes.schindelin@gmx.de>\ndiff --git a/git-notes.sh b/git-notes.sh\nindex f06c254..e642e47 100755\n--- a/git-notes.sh\n+++ b/git-notes.sh\n@@ -1,16 +1,59 @@\n #!/bin/sh\n \n-USAGE=\"(edit | show) [commit]\"\n+USAGE=\"(edit [-F <file> | -m <msg>] | show) [commit]\"\n . git-sh-setup\n \n-test -n \"$3\" && usage\n-\n test -z \"$1\" && usage\n ACTION=\"$1\"; shift\n \n test -z \"$GIT_NOTES_REF\" && GIT_NOTES_REF=\"$(git config core.notesref)\"\n test -z \"$GIT_NOTES_REF\" && GIT_NOTES_REF=\"refs/notes/commits\"\n \n+MESSAGE=\n+while test $# != 0\n+do\n+\tcase \"$1\" in\n+\t-m)\n+\t\ttest \"$ACTION\" = \"edit\" || usage\n+\t\tshift\n+\t\tif test \"$#\" = \"0\"; then\n+\t\t\tdie \"error: option -m needs an argument\"\n+\t\telse\n+\t\t\tif [ -z \"$MESSAGE\" ]; then\n+\t\t\t\tMESSAGE=\"$1\"\n+\t\t\telse\n+\t\t\t\tMESSAGE=\"$MESSAGE\n+\n+$1\"\n+\t\t\tfi\n+\t\t\tshift\n+\t\tfi\n+\t\t;;\n+\t-F)\n+\t\ttest \"$ACTION\" = \"edit\" || usage\n+\t\tshift\n+\t\tif test \"$#\" = \"0\"; then\n+\t\t\tdie \"error: option -F needs an argument\"\n+\t\telse\n+\t\t\tif [ -z \"$MESSAGE\" ]; then\n+\t\t\t\tMESSAGE=\"$(cat \"$1\")\"\n+\t\t\telse\n+\t\t\t\tMESSAGE=\"$MESSAGE\n+\n+$(cat \"$1\")\"\n+\t\t\tfi\n+\t\t\tshift\n+\t\tfi\n+\t\t;;\n+\t-*)\n+\t\tusage\n+\t\t;;\n+\t*)\n+\t\tbreak\n+\t\t;;\n+\tesac\n+done\n+\n COMMIT=$(git rev-parse --verify --default HEAD \"$@\") ||\n die \"Invalid commit: $@\"\n \n@@ -29,19 +72,24 @@ edit)\n \t\ttest -f \"$GIT_INDEX_FILE\" && rm \"$GIT_INDEX_FILE\"\n \t' 0\n \n-\tGIT_NOTES_REF= git log -1 $COMMIT | sed \"s/^/#/\" > \"$MSG_FILE\"\n-\n \tCURRENT_HEAD=$(git show-ref \"$GIT_NOTES_REF\" | cut -f 1 -d ' ')\n \tif [ -z \"$CURRENT_HEAD\" ]; then\n \t\tPARENT=\n \telse\n \t\tPARENT=\"-p $CURRENT_HEAD\"\n \t\tgit read-tree \"$GIT_NOTES_REF\" || die \"Could not read index\"\n-\t\tgit cat-file blob :$COMMIT >> \"$MSG_FILE\" 2> /dev/null\n \tfi\n \n-\tcore_editor=\"$(git config core.editor)\"\n-\t${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} \"$MSG_FILE\"\n+\tif [ -z \"$MESSAGE\" ]; then\n+\t\tGIT_NOTES_REF= git log -1 $COMMIT | sed \"s/^/#/\" > \"$MSG_FILE\"\n+\t\tif [ ! -z \"$CURRENT_HEAD\" ]; then\n+\t\t\tgit cat-file blob :$COMMIT >> \"$MSG_FILE\" 2> /dev/null\n+\t\tfi\n+\t\tcore_editor=\"$(git config core.editor)\"\n+\t\t${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} \"$MSG_FILE\"\n+\telse\n+\t\techo \"$MESSAGE\" > \"$MSG_FILE\"\n+\tfi\n \n \tgrep -v ^# < \"$MSG_FILE\" | git stripspace > \"$MSG_FILE\".processed\n \tmv \"$MSG_FILE\".processed \"$MSG_FILE\"\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 73e53be..1e34f48 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -110,5 +110,41 @@ test_expect_success 'show multi-line notes' '\n \tgit log -2 > output &&\n \ttest_cmp expect-multiline output\n '\n+test_expect_success 'create -m and -F notes (setup)' '\n+\t: > a4 &&\n+\tgit add a4 &&\n+\ttest_tick &&\n+\tgit commit -m 4th &&\n+\techo \"xyzzy\" > note5 &&\n+\tgit notes edit -m spam -F note5 -m \"foo\n+bar\n+baz\"\n+'\n+\n+whitespace=\"    \"\n+cat > expect-m-and-F << EOF\n+commit 15023535574ded8b1a89052b32673f84cf9582b8\n+Author: A U Thor <author@example.com>\n+Date:   Thu Apr 7 15:16:13 2005 -0700\n+\n+    4th\n+\n+Notes:\n+    spam\n+$whitespace\n+    xyzzy\n+$whitespace\n+    foo\n+    bar\n+    baz\n+EOF\n+\n+printf \"\\n\" >> expect-m-and-F\n+cat expect-multiline >> expect-m-and-F\n+\n+test_expect_success 'show -m and -F notes' '\n+\tgit log -3 > output &&\n+\ttest_cmp expect-m-and-F output\n+'\n \n test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124456","messageId":"1255083738-23263-8-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 06/22] fast-import: Add support for importing commit notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:02Z","receivedAt":"2009-10-09T10:22:02Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Introduce a 'notemodify' subcommand of the 'commit' command. This subcommand\nis similar to 'filemodify', except that no mode is supplied (all notes have\nmode 0644), and the path is set to the hex SHA1 of the given \"comittish\".\n\nThis enables fast import of note objects along with their associated commits,\nsince the notes can now be named using the mark references of their\ncorresponding commits.\n\nThe patch also includes a test case of the added functionality.\n\nSigned-off-by: Johan Herland <johan@herland.net>\nAcked-by: Shawn O. Pearce <spearce@spearce.org>\n---\n Documentation/git-fast-import.txt |   45 +++++++++--\n fast-import.c                     |   88 +++++++++++++++++++-\n t/t9300-fast-import.sh            |  166 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 289 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex f1c94b4..bb198c2 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -325,7 +325,7 @@ change to the project.\n \tdata\n \t('from' SP <committish> LF)?\n \t('merge' SP <committish> LF)?\n-\t(filemodify | filedelete | filecopy | filerename | filedeleteall)*\n+\t(filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*\n \tLF?\n ....\n \n@@ -348,14 +348,13 @@ commit message use a 0 length data.  Commit messages are free-form\n and are not interpreted by Git.  Currently they must be encoded in\n UTF-8, as fast-import does not permit other encodings to be specified.\n \n-Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`\n-and `filedeleteall` commands\n+Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`,\n+`filedeleteall` and `notemodify` commands\n may be included to update the contents of the branch prior to\n creating the commit.  These commands may be supplied in any order.\n However it is recommended that a `filedeleteall` command precede\n-all `filemodify`, `filecopy` and `filerename` commands in the same\n-commit, as `filedeleteall`\n-wipes the branch clean (see below).\n+all `filemodify`, `filecopy`, `filerename` and `notemodify` commands in\n+the same commit, as `filedeleteall` wipes the branch clean (see below).\n \n The `LF` after the command is optional (it used to be required).\n \n@@ -604,6 +603,40 @@ more memory per active branch (less than 1 MiB for even most large\n projects); so frontends that can easily obtain only the affected\n paths for a commit are encouraged to do so.\n \n+`notemodify`\n+^^^^^^^^^^^^\n+Included in a `commit` command to add a new note (annotating a given\n+commit) or change the content of an existing note.  This command has\n+two different means of specifying the content of the note.\n+\n+External data format::\n+\tThe data content for the note was already supplied by a prior\n+\t`blob` command.  The frontend just needs to connect it to the\n+\tcommit that is to be annotated.\n++\n+....\n+\t'N' SP <dataref> SP <committish> LF\n+....\n++\n+Here `<dataref>` can be either a mark reference (`:<idnum>`)\n+set by a prior `blob` command, or a full 40-byte SHA-1 of an\n+existing Git blob object.\n+\n+Inline data format::\n+\tThe data content for the note has not been supplied yet.\n+\tThe frontend wants to supply it as part of this modify\n+\tcommand.\n++\n+....\n+\t'N' SP 'inline' SP <committish> LF\n+\tdata\n+....\n++\n+See below for a detailed description of the `data` command.\n+\n+In both formats `<committish>` is any of the commit specification\n+expressions also accepted by `from` (see above).\n+\n `mark`\n ~~~~~~\n Arranges for fast-import to save a reference to the current object, allowing\ndiff --git a/fast-import.c b/fast-import.c\nindex 992220e..fcdcfaa 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -22,8 +22,8 @@ Format of STDIN stream:\n     ('author' sp name sp '<' email '>' sp when lf)?\n     'committer' sp name sp '<' email '>' sp when lf\n     commit_msg\n-    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?\n-    ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*\n+    ('from' sp committish lf)?\n+    ('merge' sp committish lf)*\n     file_change*\n     lf?;\n   commit_msg ::= data;\n@@ -41,15 +41,18 @@ Format of STDIN stream:\n   file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;\n   file_inm ::= 'M' sp mode sp 'inline' sp path_str lf\n     data;\n+  note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;\n+  note_inm ::= 'N' sp 'inline' sp committish lf\n+    data;\n \n   new_tag ::= 'tag' sp tag_str lf\n-    'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf\n+    'from' sp committish lf\n     ('tagger' sp name sp '<' email '>' sp when lf)?\n     tag_msg;\n   tag_msg ::= data;\n \n   reset_branch ::= 'reset' sp ref_str lf\n-    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?\n+    ('from' sp committish lf)?\n     lf?;\n \n   checkpoint ::= 'checkpoint' lf\n@@ -88,6 +91,7 @@ Format of STDIN stream:\n      # stream formatting is: \\, \" and LF.  Otherwise these values\n      # are UTF8.\n      #\n+  committish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);\n   ref_str     ::= ref;\n   sha1exp_str ::= sha1exp;\n   tag_str     ::= tag;\n@@ -2056,6 +2060,80 @@ 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+{\n+\tconst char *p = command_buf.buf + 2;\n+\tstatic struct strbuf uq = STRBUF_INIT;\n+\tstruct object_entry *oe = oe;\n+\tstruct branch *s;\n+\tunsigned char sha1[20], commit_sha1[20];\n+\tuint16_t inline_data = 0;\n+\n+\t/* <dataref> or 'inline' */\n+\tif (*p == ':') {\n+\t\tchar *x;\n+\t\toe = find_mark(strtoumax(p + 1, &x, 10));\n+\t\thashcpy(sha1, oe->sha1);\n+\t\tp = x;\n+\t} else if (!prefixcmp(p, \"inline\")) {\n+\t\tinline_data = 1;\n+\t\tp += 6;\n+\t} else {\n+\t\tif (get_sha1_hex(p, sha1))\n+\t\t\tdie(\"Invalid SHA1: %s\", command_buf.buf);\n+\t\toe = find_object(sha1);\n+\t\tp += 40;\n+\t}\n+\tif (*p++ != ' ')\n+\t\tdie(\"Missing space after SHA1: %s\", command_buf.buf);\n+\n+\t/* <committish> */\n+\ts = lookup_branch(p);\n+\tif (s) {\n+\t\thashcpy(commit_sha1, s->sha1);\n+\t} else if (*p == ':') {\n+\t\tuintmax_t commit_mark = strtoumax(p + 1, NULL, 10);\n+\t\tstruct object_entry *commit_oe = find_mark(commit_mark);\n+\t\tif (commit_oe->type != OBJ_COMMIT)\n+\t\t\tdie(\"Mark :%\" PRIuMAX \" not a commit\", commit_mark);\n+\t\thashcpy(commit_sha1, commit_oe->sha1);\n+\t} else if (!get_sha1(p, commit_sha1)) {\n+\t\tunsigned long size;\n+\t\tchar *buf = read_object_with_reference(commit_sha1,\n+\t\t\tcommit_type, &size, commit_sha1);\n+\t\tif (!buf || size < 46)\n+\t\t\tdie(\"Not a valid commit: %s\", p);\n+\t\tfree(buf);\n+\t} else\n+\t\tdie(\"Invalid ref name or SHA1 expression: %s\", p);\n+\n+\tif (inline_data) {\n+\t\tstatic struct strbuf buf = STRBUF_INIT;\n+\n+\t\tif (p != uq.buf) {\n+\t\t\tstrbuf_addstr(&uq, p);\n+\t\t\tp = uq.buf;\n+\t\t}\n+\t\tread_next_command();\n+\t\tparse_data(&buf);\n+\t\tstore_object(OBJ_BLOB, &buf, &last_blob, sha1, 0);\n+\t} else if (oe) {\n+\t\tif (oe->type != OBJ_BLOB)\n+\t\t\tdie(\"Not a blob (actually a %s): %s\",\n+\t\t\t\ttypename(oe->type), command_buf.buf);\n+\t} else {\n+\t\tenum object_type type = sha1_object_info(sha1, NULL);\n+\t\tif (type < 0)\n+\t\t\tdie(\"Blob not found: %s\", command_buf.buf);\n+\t\tif (type != OBJ_BLOB)\n+\t\t\tdie(\"Not a blob (actually a %s): %s\",\n+\t\t\t    typename(type), command_buf.buf);\n+\t}\n+\n+\ttree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,\n+\t\tS_IFREG | 0644, NULL);\n+}\n+\n static void file_change_deleteall(struct branch *b)\n {\n \trelease_tree_content_recursive(b->branch_tree.tree);\n@@ -2225,6 +2303,8 @@ 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\t\tfile_change_deleteall(b);\n \t\telse {\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex d33fc55..2f5c323 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -1089,6 +1089,172 @@ test_expect_success 'P: fail on blob mark in gitlink' '\n     test_must_fail git fast-import <input'\n \n ###\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+\n+test_tick\n+cat >input <<INPUT_END\n+blob\n+mark :2\n+data <<EOF\n+$file2_data\n+EOF\n+\n+commit refs/heads/notes-test\n+mark :3\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+first (:3)\n+COMMIT\n+\n+M 644 :2 file2\n+\n+blob\n+mark :4\n+data $file4_len\n+$file4_data\n+commit refs/heads/notes-test\n+mark :5\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+second (:5)\n+COMMIT\n+\n+M 644 :4 file4\n+\n+commit refs/heads/notes-test\n+mark :6\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+third (:6)\n+COMMIT\n+\n+M 644 inline file5\n+data <<EOF\n+$file5_data\n+EOF\n+\n+M 755 inline file6\n+data <<EOF\n+$file6_data\n+EOF\n+\n+blob\n+mark :7\n+data <<EOF\n+$note1_data\n+EOF\n+\n+blob\n+mark :8\n+data <<EOF\n+$note2_data\n+EOF\n+\n+commit refs/notes/foobar\n+mark :9\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes (:9)\n+COMMIT\n+\n+N :7 :3\n+N :8 :5\n+N inline :6\n+data <<EOF\n+$note3_data\n+EOF\n+\n+INPUT_END\n+test_expect_success \\\n+\t'Q: commit notes' \\\n+\t'git fast-import <input &&\n+\t git whatchanged notes-test'\n+test_expect_success \\\n+\t'Q: verify pack' \\\n+\t'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'\n+\n+commit1=$(git rev-parse notes-test~2)\n+commit2=$(git rev-parse notes-test^)\n+commit3=$(git rev-parse notes-test)\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+first (:3)\n+EOF\n+test_expect_success \\\n+\t'Q: verify first commit' \\\n+\t'git cat-file commit notes-test~2 | sed 1d >actual &&\n+\ttest_cmp expect actual'\n+\n+cat >expect <<EOF\n+parent $commit1\n+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\n+second (:5)\n+EOF\n+test_expect_success \\\n+\t'Q: verify second commit' \\\n+\t'git cat-file commit notes-test^ | sed 1d >actual &&\n+\ttest_cmp expect actual'\n+\n+cat >expect <<EOF\n+parent $commit2\n+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+\n+third (:6)\n+EOF\n+test_expect_success \\\n+\t'Q: verify third commit' \\\n+\t'git cat-file commit notes-test | sed 1d >actual &&\n+\ttest_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 (: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+\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 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 \"$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+\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+\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+\n+###\n ### series R (feature and option)\n ###\n \n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124466","messageId":"1255083738-23263-9-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 07/22] t3302-notes-index-expensive: Speed up create_repo()","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:03Z","receivedAt":"2009-10-09T10:22:03Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Creating repos with 10/100/1000/10000 commits and notes takes a lot of time.\nHowever, using git-fast-import to do the job is a lot more efficient than\nusing plumbing commands to do the same.\n\nThis patch decreases the overall run-time of this test on my machine from\n~3 to ~1 minutes.\n\nSigned-off-by: Johan Herland <johan@herland.net>\nAcked-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3302-notes-index-expensive.sh |   74 ++++++++++++++++++++++++--------------\n 1 files changed, 47 insertions(+), 27 deletions(-)\n\ndiff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh\nindex 0ef3e95..ee84fc4 100755\n--- a/t/t3302-notes-index-expensive.sh\n+++ b/t/t3302-notes-index-expensive.sh\n@@ -16,30 +16,50 @@ test -z \"$GIT_NOTES_TIMING_TESTS\" && {\n create_repo () {\n \tnumber_of_commits=$1\n \tnr=0\n-\tparent=\n \ttest -d .git || {\n \tgit init &&\n-\ttree=$(git write-tree) &&\n-\twhile [ $nr -lt $number_of_commits ]; do\n-\t\ttest_tick &&\n-\t\tcommit=$(echo $nr | git commit-tree $tree $parent) ||\n-\t\t\treturn\n-\t\tparent=\"-p $commit\"\n-\t\tnr=$(($nr+1))\n-\tdone &&\n-\tgit update-ref refs/heads/master $commit &&\n-\t{\n-\t\tGIT_INDEX_FILE=.git/temp; export GIT_INDEX_FILE;\n-\t\tgit rev-list HEAD | cat -n | sed \"s/^[ \t][ \t]*/ /g\" |\n-\t\twhile read nr sha1; do\n-\t\t\tblob=$(echo note $nr | git hash-object -w --stdin) &&\n-\t\t\techo $sha1 | sed \"s/^/0644 $blob 0\t/\"\n-\t\tdone | git update-index --index-info &&\n-\t\ttree=$(git write-tree) &&\n+\t(\n+\t\twhile [ $nr -lt $number_of_commits ]; do\n+\t\t\tnr=$(($nr+1))\n+\t\t\tmark=$(($nr+$nr))\n+\t\t\tnotemark=$(($mark+1))\n+\t\t\ttest_tick &&\n+\t\t\tcat <<INPUT_END &&\n+commit refs/heads/master\n+mark :$mark\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+commit #$nr\n+COMMIT\n+\n+M 644 inline file\n+data <<EOF\n+file in commit #$nr\n+EOF\n+\n+blob\n+mark :$notemark\n+data <<EOF\n+note for commit #$nr\n+EOF\n+\n+INPUT_END\n+\n+\t\t\techo \"N :$notemark :$mark\" >> note_commit\n+\t\tdone &&\n \t\ttest_tick &&\n-\t\tcommit=$(echo notes | git commit-tree $tree) &&\n-\t\tgit update-ref refs/notes/commits $commit\n-\t} &&\n+\t\tcat <<INPUT_END &&\n+commit refs/notes/commits\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes\n+COMMIT\n+\n+INPUT_END\n+\n+\t\tcat note_commit\n+\t) |\n+\tgit fast-import --quiet &&\n \tgit config core.notesRef refs/notes/commits\n \t}\n }\n@@ -48,13 +68,13 @@ test_notes () {\n \tcount=$1 &&\n \tgit config core.notesRef refs/notes/commits &&\n \tgit log | grep \"^    \" > output &&\n-\ti=1 &&\n-\twhile [ $i -le $count ]; do\n-\t\techo \"    $(($count-$i))\" &&\n-\t\techo \"    note $i\" &&\n-\t\ti=$(($i+1));\n+\ti=$count &&\n+\twhile [ $i -gt 0 ]; do\n+\t\techo \"    commit #$i\" &&\n+\t\techo \"    note for commit #$i\" &&\n+\t\ti=$(($i-1));\n \tdone > expect &&\n-\tgit diff expect output\n+\ttest_cmp expect output\n }\n \n cat > time_notes << \\EOF\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124468","messageId":"1255083738-23263-10-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 08/22] Add flags to get_commit_notes() to control the format of the note string","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:04Z","receivedAt":"2009-10-09T10:22:04Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"This patch adds the following flags to get_commit_notes() for adjusting the\nformat of the produced note string:\n- NOTES_SHOW_HEADER: Print \"Notes:\" line before the notes contents\n- NOTES_INDENT: Indent notes contents by 4 spaces\n\nSuggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c  |    8 +++++---\n notes.h  |    5 ++++-\n pretty.c |    3 ++-\n 3 files changed, 11 insertions(+), 5 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 2b66723..b7d79e1 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -106,7 +106,7 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1)\n }\n \n void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n-\t\tconst char *output_encoding)\n+\t\tconst char *output_encoding, int flags)\n {\n \tstatic const char utf8[] = \"utf-8\";\n \tunsigned char *sha1;\n@@ -148,12 +148,14 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n \tif (msglen && msg[msglen - 1] == '\\n')\n \t\tmsglen--;\n \n-\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n+\tif (flags & NOTES_SHOW_HEADER)\n+\t\tstrbuf_addstr(sb, \"\\nNotes:\\n\");\n \n \tfor (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {\n \t\tlinelen = strchrnul(msg_p, '\\n') - msg_p;\n \n-\t\tstrbuf_addstr(sb, \"    \");\n+\t\tif (flags & NOTES_INDENT)\n+\t\t\tstrbuf_addstr(sb, \"    \");\n \t\tstrbuf_add(sb, msg_p, linelen);\n \t\tstrbuf_addch(sb, '\\n');\n \t}\ndiff --git a/notes.h b/notes.h\nindex 79d21b6..7f3eed4 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -1,7 +1,10 @@\n #ifndef NOTES_H\n #define NOTES_H\n \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-\t\tconst char *output_encoding);\n+\t\tconst char *output_encoding, int flags);\n \n #endif\ndiff --git a/pretty.c b/pretty.c\nindex e25db81..01eadd0 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -978,7 +978,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\tget_commit_notes(commit, sb, encoding,\n+\t\t\t\t NOTES_SHOW_HEADER | NOTES_INDENT);\n \n \tfree(reencoded);\n }\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124459","messageId":"1255083738-23263-11-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 09/22] Add '%N'-format for pretty-printing commit notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:05Z","receivedAt":"2009-10-09T10:22:05Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n Documentation/pretty-formats.txt |    1 +\n pretty.c                         |    4 ++++\n 2 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 2a845b1..5fb10b3 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -123,6 +123,7 @@ The placeholders are:\n - '%s': subject\n - '%f': sanitized subject line, suitable for a filename\n - '%b': body\n+- '%N': commit notes\n - '%Cred': switch color to red\n - '%Cgreen': switch color to green\n - '%Cblue': switch color to blue\ndiff --git a/pretty.c b/pretty.c\nindex 01eadd0..7f350bb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -702,6 +702,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,\n \tcase 'd':\n \t\tformat_decoration(sb, commit);\n \t\treturn 1;\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\treturn 1;\n \t}\n \n \t/* For the rest we have to parse the commit header. */\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124458","messageId":"1255083738-23263-12-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 10/22] Teach notes code to free its internal data structures on request","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:06Z","receivedAt":"2009-10-09T10:22:06Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"There's no need to be rude to memory-concious callers...\n\nThis patch has been improved by the following contributions:\n- Junio C Hamano: avoid old-style declaration\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |    7 +++++++\n notes.h |    3 +++\n 2 files changed, 10 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex b7d79e1..a5d888d 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -105,6 +105,13 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1)\n \treturn hash_map.entries[index].notes_sha1;\n }\n\n+void free_notes(void)\n+{\n+\tfree(hash_map.entries);\n+\tmemset(&hash_map, 0, sizeof(struct hash_map));\n+\tinitialized = 0;\n+}\n+\n void get_commit_notes(const struct commit *commit, struct strbuf *sb,\n \t\tconst char *output_encoding, int flags)\n {\ndiff --git a/notes.h b/notes.h\nindex 7f3eed4..a1421e3 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -1,6 +1,9 @@\n #ifndef NOTES_H\n #define NOTES_H\n\n+/* Free (and de-initialize) the internal notes tree structure */\n+void free_notes(void);\n+\n #define NOTES_SHOW_HEADER 1\n #define NOTES_INDENT 2\n\n--\n1.6.4.304.g1365c.dirty\n"},{"id":"124473","messageId":"1255083738-23263-13-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 11/22] Teach the notes lookup code to parse notes trees with various fanout schemes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:07Z","receivedAt":"2009-10-09T10:22:07Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"The semantics used when parsing notes trees (with regards to fanout subtrees)\nfollow Dscho's proposal fairly closely:\n- No concatenation/merging of notes is performed. If there are several notes\n  objects referencing a given commit, only one of those objects are used.\n- If a notes object for a given commit is present in the \"root\" notes tree,\n  no subtrees are consulted; the object in the root tree is used directly.\n- If there are more than one subtree that prefix-matches the given commit,\n  only the subtree with the longest matching prefix is consulted. This\n  means that if the given commit is e.g. \"deadbeef\", and the notes tree have\n  subtrees \"de\" and \"dead\", then the following paths in the notes tree are\n  searched: \"deadbeef\", \"dead/beef\". Note that \"de/adbeef\" is NOT searched.\n- Fanout directories (subtrees) must references a whole number of bytes\n  from the SHA1 sum they subdivide. E.g. subtrees \"dead\" and \"de\" are\n  acceptable; \"d\" and \"dea\" are not.\n- Multiple levels of fanout are allowed. All the above rules apply\n  recursively. E.g. \"de/adbeef\" is preferred over \"de/adbe/ef\", etc.\n\nThis patch changes the in-memory datastructure for holding parsed notes:\nInstead of holding all note (and subtree) entries in a hash table, a\nsimple 16-tree structure is used instead. The tree structure consists of\n16-arrays as internal nodes, and note/subtree entries as leaf nodes. The\ntree is traversed by indexing subsequent nibbles of the search key until\na leaf node is encountered. If a subtree entry is encountered while\nsearching for a note, the subtree is unpacked into the 16-tree structure,\nand the search continues into that subtree.\n\nThe new algorithm performs significantly better in the cases where only\na fraction of the notes need to be looked up (this is assumed to be the\ncommon case for notes lookup). The new code even performs marginally\nbetter in the worst case (where _all_ the notes are looked up).\n\nIn addition to this, comes the massive performance win associated with\norganizing the notes tree according to some fanout scheme. Even a simple\n2/38 fanout scheme is dramatically quicker to traverse (going from tens of\nseconds to sub-second runtimes).\n\nAs for memory usage, the new code is marginally better than the old code in\nthe worst case, but in the case of looking up only some notes from a notes\ntree with proper fanout, the new code uses only a small fraction of the\nmemory needed to hold the entire notes tree.\n\nHowever, there is one casualty of this patch. The old notes lookup code was\nable to parse notes that were associated with non-SHA1s (e.g. refs). The new\ncode requires the referenced object to be named by a SHA1 sum. Still, this\nis not considered a major setback, since the notes infrastructure was not\noriginally intended to annotate objects outside the Git object database.\n\nCc: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  317 +++++++++++++++++++++++++++++++++++++++++++++++++--------------\n 1 files changed, 248 insertions(+), 69 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex a5d888d..210c4b2 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -6,109 +6,288 @@\n #include \"strbuf.h\"\n #include \"tree-walk.h\"\n \n-struct entry {\n-\tunsigned char commit_sha1[20];\n-\tunsigned char notes_sha1[20];\n+/*\n+ * Use a non-balancing simple 16-tree structure with struct int_node as\n+ * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a\n+ * 16-array of pointers to its children.\n+ * The bottom 2 bits of each pointer is used to identify the pointer type\n+ * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL)\n+ * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node *\n+ * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node *\n+ * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node *\n+ *\n+ * The root node is a statically allocated struct int_node.\n+ */\n+struct int_node {\n+\tvoid *a[16];\n };\n \n-struct hash_map {\n-\tstruct entry *entries;\n-\toff_t count, size;\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+ * 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+ * the prefix. The value is the SHA1 of the tree object containing the notes\n+ * subtree.\n+ */\n+struct leaf_node {\n+\tunsigned char key_sha1[20];\n+\tunsigned char val_sha1[20];\n };\n \n-static int initialized;\n-static struct hash_map hash_map;\n+#define PTR_TYPE_NULL     0\n+#define PTR_TYPE_INTERNAL 1\n+#define PTR_TYPE_NOTE     2\n+#define PTR_TYPE_SUBTREE  3\n \n-static int hash_index(struct hash_map *map, const unsigned char *sha1)\n-{\n-\tint i = ((*(unsigned int *)sha1) % map->size);\n+#define GET_PTR_TYPE(ptr)       ((uintptr_t) (ptr) & 3)\n+#define CLR_PTR_TYPE(ptr)       ((void *) ((uintptr_t) (ptr) & ~3))\n+#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type)))\n \n-\tfor (;;) {\n-\t\tunsigned char *current = map->entries[i].commit_sha1;\n+#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f)\n \n-\t\tif (!hashcmp(sha1, current))\n-\t\t\treturn i;\n+#define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \\\n+\t(memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))\n \n-\t\tif (is_null_sha1(current))\n-\t\t\treturn -1 - i;\n+static struct int_node root_node;\n \n-\t\tif (++i == map->size)\n-\t\t\ti = 0;\n+static int initialized;\n+\n+static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n+\t\tunsigned int n);\n+\n+/*\n+ * To find a leaf_node:\n+ * 1. Start at the root node, with n = 0\n+ * 2. Use the nth nibble of the key as an index into a:\n+ *    - If a[n] is an int_node, recurse into that node and increment n\n+ *    - If a leaf_node with matching key, return leaf_node (assert note entry)\n+ *    - If a matching subtree entry, unpack that subtree entry (and remove it);\n+ *      restart search at the current level.\n+ *    - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node.\n+ *      Backtrack out of the recursion, one level at a time and check a[0]:\n+ *      - If a[0] at the current level is a matching subtree entry, unpack that\n+ *        subtree entry (and remove it); restart search at the current level.\n+ */\n+static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n+\t\tconst unsigned char *key_sha1)\n+{\n+\tstruct leaf_node *l;\n+\tunsigned char i = GET_NIBBLE(n, key_sha1);\n+\tvoid *p = tree->a[i];\n+\n+\tswitch(GET_PTR_TYPE(p)) {\n+\tcase PTR_TYPE_INTERNAL:\n+\t\tl = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1);\n+\t\tif (l)\n+\t\t\treturn l;\n+\t\tbreak;\n+\tcase PTR_TYPE_NOTE:\n+\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\tif (!hashcmp(key_sha1, l->key_sha1))\n+\t\t\treturn l; /* return note object matching given key */\n+\t\tbreak;\n+\tcase PTR_TYPE_SUBTREE:\n+\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n+\t\t\t/* unpack tree and resume search */\n+\t\t\ttree->a[i] = NULL;\n+\t\t\tload_subtree(l, tree, n);\n+\t\t\tfree(l);\n+\t\t\treturn note_tree_find(tree, n, key_sha1);\n+\t\t}\n+\t\tbreak;\n+\tcase PTR_TYPE_NULL:\n+\tdefault:\n+\t\tassert(!p);\n+\t\tbreak;\n \t}\n+\n+\t/*\n+\t * Did not find key at this (or any lower) level.\n+\t * Check if there's a matching subtree entry in tree->a[0].\n+\t * If so, unpack tree and resume search.\n+\t */\n+\tp = tree->a[0];\n+\tif (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE)\n+\t\treturn NULL;\n+\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n+\t\t/* unpack tree and resume search */\n+\t\ttree->a[0] = NULL;\n+\t\tload_subtree(l, tree, n);\n+\t\tfree(l);\n+\t\treturn note_tree_find(tree, n, key_sha1);\n+\t}\n+\treturn NULL;\n }\n \n-static void add_entry(const unsigned char *commit_sha1,\n-\t\tconst unsigned char *notes_sha1)\n+/*\n+ * To insert a leaf_node:\n+ * 1. Start at the root node, with n = 0\n+ * 2. Use the nth nibble of the key as an index into a:\n+ *    - If a[n] is NULL, store the tweaked pointer directly into a[n]\n+ *    - If a[n] is an int_node, recurse into that node and increment n\n+ *    - If a[n] is a leaf_node:\n+ *      1. Check if they're equal, and handle that (abort? overwrite?)\n+ *      2. Create a new int_node, and store both leaf_nodes there\n+ *      3. Store the new int_node into a[n].\n+ */\n+static int note_tree_insert(struct int_node *tree, unsigned char n,\n+\t\tconst struct leaf_node *entry, unsigned char type)\n {\n-\tint index;\n-\n-\tif (hash_map.count + 1 > hash_map.size >> 1) {\n-\t\tint i, old_size = hash_map.size;\n-\t\tstruct entry *old = hash_map.entries;\n-\n-\t\thash_map.size = old_size ? old_size << 1 : 64;\n-\t\thash_map.entries = (struct entry *)\n-\t\t\txcalloc(sizeof(struct entry), hash_map.size);\n-\n-\t\tfor (i = 0; i < old_size; i++)\n-\t\t\tif (!is_null_sha1(old[i].commit_sha1)) {\n-\t\t\t\tindex = -1 - hash_index(&hash_map,\n-\t\t\t\t\t\told[i].commit_sha1);\n-\t\t\t\tmemcpy(hash_map.entries + index, old + i,\n-\t\t\t\t\tsizeof(struct entry));\n-\t\t\t}\n-\t\tfree(old);\n+\tstruct int_node *new_node;\n+\tconst struct leaf_node *l;\n+\tint ret;\n+\tunsigned char i = GET_NIBBLE(n, entry->key_sha1);\n+\tvoid *p = tree->a[i];\n+\tassert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL);\n+\tswitch(GET_PTR_TYPE(p)) {\n+\tcase PTR_TYPE_NULL:\n+\t\tassert(!p);\n+\t\ttree->a[i] = SET_PTR_TYPE(entry, type);\n+\t\treturn 0;\n+\tcase PTR_TYPE_INTERNAL:\n+\t\treturn note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type);\n+\tdefault:\n+\t\tassert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE ||\n+\t\t\tGET_PTR_TYPE(p) == PTR_TYPE_SUBTREE);\n+\t\tl = (const struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\tif (!hashcmp(entry->key_sha1, l->key_sha1))\n+\t\t\treturn -1; /* abort insert on matching key */\n+\t\tnew_node = (struct int_node *)\n+\t\t\txcalloc(sizeof(struct int_node), 1);\n+\t\tret = note_tree_insert(new_node, n + 1,\n+\t\t\tCLR_PTR_TYPE(p), GET_PTR_TYPE(p));\n+\t\tif (ret) {\n+\t\t\tfree(new_node);\n+\t\t\treturn -1;\n+\t\t}\n+\t\ttree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);\n+\t\treturn note_tree_insert(new_node, n + 1, entry, type);\n \t}\n+}\n \n-\tindex = hash_index(&hash_map, commit_sha1);\n-\tif (index < 0) {\n-\t\tindex = -1 - index;\n-\t\thash_map.count++;\n+/* Free the entire notes data contained in the given tree */\n+static void note_tree_free(struct int_node *tree)\n+{\n+\tunsigned int i;\n+\tfor (i = 0; i < 16; i++) {\n+\t\tvoid *p = tree->a[i];\n+\t\tswitch(GET_PTR_TYPE(p)) {\n+\t\tcase PTR_TYPE_INTERNAL:\n+\t\t\tnote_tree_free(CLR_PTR_TYPE(p));\n+\t\t\t/* fall through */\n+\t\tcase PTR_TYPE_NOTE:\n+\t\tcase PTR_TYPE_SUBTREE:\n+\t\t\tfree(CLR_PTR_TYPE(p));\n+\t\t}\n \t}\n+}\n \n-\thashcpy(hash_map.entries[index].commit_sha1, commit_sha1);\n-\thashcpy(hash_map.entries[index].notes_sha1, notes_sha1);\n+/*\n+ * Convert a partial SHA1 hex string to the corresponding partial SHA1 value.\n+ * - hex      - Partial SHA1 segment in ASCII hex format\n+ * - hex_len  - Length of above segment. Must be multiple of 2 between 0 and 40\n+ * - sha1     - Partial SHA1 value is written here\n+ * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20\n+ * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format).\n+ * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2).\n+ * Pads sha1 with NULs up to sha1_len (not included in returned length).\n+ */\n+static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,\n+\t\tunsigned char *sha1, unsigned int sha1_len)\n+{\n+\tunsigned int i, len = hex_len >> 1;\n+\tif (hex_len % 2 != 0 || len > sha1_len)\n+\t\treturn -1;\n+\tfor (i = 0; i < len; i++) {\n+\t\tunsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);\n+\t\tif (val & ~0xff)\n+\t\t\treturn -1;\n+\t\t*sha1++ = val;\n+\t\thex += 2;\n+\t}\n+\tfor (; i < sha1_len; i++)\n+\t\t*sha1++ = 0;\n+\treturn len;\n }\n \n-static void initialize_hash_map(const char *notes_ref_name)\n+static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n+\t\tunsigned int n)\n {\n-\tunsigned char sha1[20], commit_sha1[20];\n-\tunsigned mode;\n+\tunsigned char commit_sha1[20];\n+\tunsigned int prefix_len;\n+\tint status;\n+\tvoid *buf;\n \tstruct tree_desc desc;\n \tstruct name_entry entry;\n-\tvoid *buf;\n+\n+\tbuf = fill_tree_descriptor(&desc, subtree->val_sha1);\n+\tif (!buf)\n+\t\tdie(\"Could not read %s for notes-index\",\n+\t\t     sha1_to_hex(subtree->val_sha1));\n+\n+\tprefix_len = subtree->key_sha1[19];\n+\tassert(prefix_len * 2 >= n);\n+\tmemcpy(commit_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\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 */\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->val_sha1, entry.sha1);\n+\t\t\tif (len < 20) {\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\tstatus = note_tree_insert(node, n, l, type);\n+\t\t\tassert(!status);\n+\t\t}\n+\t}\n+\tfree(buf);\n+}\n+\n+static void initialize_notes(const char *notes_ref_name)\n+{\n+\tunsigned char sha1[20], commit_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 \t\treturn;\n \n-\tbuf = fill_tree_descriptor(&desc, sha1);\n-\tif (!buf)\n-\t\tdie(\"Could not read %s for notes-index\", sha1_to_hex(sha1));\n-\n-\twhile (tree_entry(&desc, &entry))\n-\t\tif (!get_sha1(entry.path, commit_sha1))\n-\t\t\tadd_entry(commit_sha1, entry.sha1);\n-\tfree(buf);\n+\thashclr(root_tree.key_sha1);\n+\thashcpy(root_tree.val_sha1, sha1);\n+\tload_subtree(&root_tree, &root_node, 0);\n }\n \n static unsigned char *lookup_notes(const unsigned char *commit_sha1)\n {\n-\tint index;\n-\n-\tif (!hash_map.size)\n-\t\treturn NULL;\n-\n-\tindex = hash_index(&hash_map, commit_sha1);\n-\tif (index < 0)\n-\t\treturn NULL;\n-\treturn hash_map.entries[index].notes_sha1;\n+\tstruct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1);\n+\tif (found)\n+\t\treturn found->val_sha1;\n+\treturn NULL;\n }\n \n void free_notes(void)\n {\n-\tfree(hash_map.entries);\n-\tmemset(&hash_map, 0, sizeof(struct hash_map));\n+\tnote_tree_free(&root_node);\n+\tmemset(&root_node, 0, sizeof(struct int_node));\n \tinitialized = 0;\n }\n \n@@ -127,7 +306,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,\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_hash_map(notes_ref_name);\n+\t\tinitialize_notes(notes_ref_name);\n \t\tinitialized = 1;\n \t}\n \n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124472","messageId":"1255083738-23263-14-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 12/22] Add selftests verifying that we can parse notes trees with various fanouts","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:08Z","receivedAt":"2009-10-09T10:22:08Z","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/t3303-notes-subtrees.sh |  104 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 104 insertions(+), 0 deletions(-)\n create mode 100755 t/t3303-notes-subtrees.sh\n\ndiff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh\nnew file mode 100755\nindex 0000000..cbb9d35\n--- /dev/null\n+++ b/t/t3303-notes-subtrees.sh\n@@ -0,0 +1,104 @@\n+#!/bin/sh\n+\n+test_description='Test commit notes organized in subtrees'\n+\n+. ./test-lib.sh\n+\n+number_of_commits=100\n+\n+start_note_commit () {\n+\ttest_tick &&\n+\tcat <<INPUT_END\n+commit refs/notes/commits\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+notes\n+COMMIT\n+\n+from refs/notes/commits^0\n+deleteall\n+INPUT_END\n+\n+}\n+\n+verify_notes () {\n+\tgit log | grep \"^    \" > output &&\n+\ti=$number_of_commits &&\n+\twhile [ $i -gt 0 ]; do\n+\t\techo \"    commit #$i\" &&\n+\t\techo \"    note for commit #$i\" &&\n+\t\ti=$(($i-1));\n+\tdone > expect &&\n+\ttest_cmp expect output\n+}\n+\n+test_expect_success \"setup: create $number_of_commits commits\" '\n+\n+\t(\n+\t\tnr=0 &&\n+\t\twhile [ $nr -lt $number_of_commits ]; do\n+\t\t\tnr=$(($nr+1)) &&\n+\t\t\ttest_tick &&\n+\t\t\tcat <<INPUT_END\n+commit refs/heads/master\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+commit #$nr\n+COMMIT\n+\n+M 644 inline file\n+data <<EOF\n+file in commit #$nr\n+EOF\n+\n+INPUT_END\n+\n+\t\tdone &&\n+\t\ttest_tick &&\n+\t\tcat <<INPUT_END\n+commit refs/notes/commits\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n+data <<COMMIT\n+no notes\n+COMMIT\n+\n+deleteall\n+\n+INPUT_END\n+\n+\t) |\n+\tgit fast-import --quiet &&\n+\tgit config core.notesRef refs/notes/commits\n+'\n+\n+test_sha1_based () {\n+\t(\n+\t\tstart_note_commit &&\n+\t\tnr=$number_of_commits &&\n+\t\tgit rev-list refs/heads/master |\n+\t\twhile read sha1; do\n+\t\t\tnote_path=$(echo \"$sha1\" | sed \"$1\")\n+\t\t\tcat <<INPUT_END &&\n+M 100644 inline $note_path\n+data <<EOF\n+note for commit #$nr\n+EOF\n+\n+INPUT_END\n+\n+\t\t\tnr=$(($nr-1))\n+\t\tdone\n+\t) |\n+\tgit fast-import --quiet\n+}\n+\n+test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based \"s|^..|&/|\"'\n+test_expect_success 'verify notes in 2/38-fanout' 'verify_notes'\n+\n+test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based \"s|^....|&/|\"'\n+test_expect_success 'verify notes in 4/36-fanout' 'verify_notes'\n+\n+test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\"'\n+test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'\n+\n+test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124470","messageId":"1255083738-23263-15-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 13/22] Refactor notes code to concatenate multiple notes annotating the same object","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:09Z","receivedAt":"2009-10-09T10:22:09Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Currently, having multiple notes referring to the same commit from various\nlocations in the notes tree is strongly discouraged, since only one of those\nnotes will be parsed and shown.\n\nThis patch teaches the notes code to _concatenate_ multiple notes that\nannotate the same commit. Notes are concatenated by creating a new blob\nobject containing the concatenation of the notes in question, and\nreplacing them with the concatenated note in the internal notes tree\nstructure.\n\nGetting the concatenation right requires being more proactive in unpacking\nsubtree entries in the internal notes tree structure, so that we don't return\na note prematurely (i.e. before having found all other notes that annotate\nthe same object). As such, this patch may incur a small performance penalty.\n\nSuggested-by: Sam Vilain <sam@vilain.net>\nRe-suggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  243 +++++++++++++++++++++++++++++++++++++++++---------------------\n 1 files changed, 161 insertions(+), 82 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 210c4b2..50a4672 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -59,115 +59,196 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n \t\tunsigned int n);\n \n /*\n- * To find a leaf_node:\n+ * Search the tree until the appropriate location for the given key is found:\n  * 1. Start at the root node, with n = 0\n- * 2. Use the nth nibble of the key as an index into a:\n- *    - If a[n] is an int_node, recurse into that node and increment n\n- *    - If a leaf_node with matching key, return leaf_node (assert note entry)\n+ * 2. If a[0] at the current level is a matching subtree entry, unpack that\n+ *    subtree entry and remove it; restart search at the current level.\n+ * 3. Use the nth nibble of the key as an index into a:\n+ *    - If a[n] is an int_node, recurse from #2 into that node and increment n\n  *    - If a matching subtree entry, unpack that subtree entry (and remove it);\n  *      restart search at the current level.\n- *    - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node.\n- *      Backtrack out of the recursion, one level at a time and check a[0]:\n- *      - If a[0] at the current level is a matching subtree entry, unpack that\n- *        subtree entry (and remove it); restart search at the current level.\n+ *    - Otherwise, we have found one of the following:\n+ *      - a subtree entry which does not match the key\n+ *      - a note entry which may or may not match the key\n+ *      - an unused leaf node (NULL)\n+ *      In any case, set *tree and *n, and return pointer to the tree location.\n  */\n-static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n-\t\tconst unsigned char *key_sha1)\n+static void **note_tree_search(struct int_node **tree,\n+\t\tunsigned char *n, const unsigned char *key_sha1)\n {\n \tstruct leaf_node *l;\n-\tunsigned char i = GET_NIBBLE(n, key_sha1);\n-\tvoid *p = tree->a[i];\n+\tunsigned char i;\n+\tvoid *p = (*tree)->a[0];\n \n+\tif (GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE) {\n+\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n+\t\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n+\t\t\t/* unpack tree and resume search */\n+\t\t\t(*tree)->a[0] = NULL;\n+\t\t\tload_subtree(l, *tree, *n);\n+\t\t\tfree(l);\n+\t\t\treturn note_tree_search(tree, n, key_sha1);\n+\t\t}\n+\t}\n+\n+\ti = GET_NIBBLE(*n, key_sha1);\n+\tp = (*tree)->a[i];\n \tswitch(GET_PTR_TYPE(p)) {\n \tcase PTR_TYPE_INTERNAL:\n-\t\tl = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1);\n-\t\tif (l)\n-\t\t\treturn l;\n-\t\tbreak;\n-\tcase PTR_TYPE_NOTE:\n-\t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n-\t\tif (!hashcmp(key_sha1, l->key_sha1))\n-\t\t\treturn l; /* return note object matching given key */\n-\t\tbreak;\n+\t\t*tree = CLR_PTR_TYPE(p);\n+\t\t(*n)++;\n+\t\treturn note_tree_search(tree, n, key_sha1);\n \tcase PTR_TYPE_SUBTREE:\n \t\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n \t\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n \t\t\t/* unpack tree and resume search */\n-\t\t\ttree->a[i] = NULL;\n-\t\t\tload_subtree(l, tree, n);\n+\t\t\t(*tree)->a[i] = NULL;\n+\t\t\tload_subtree(l, *tree, *n);\n \t\t\tfree(l);\n-\t\t\treturn note_tree_find(tree, n, key_sha1);\n+\t\t\treturn note_tree_search(tree, n, key_sha1);\n \t\t}\n-\t\tbreak;\n-\tcase PTR_TYPE_NULL:\n+\t\t/* fall through */\n \tdefault:\n-\t\tassert(!p);\n-\t\tbreak;\n+\t\treturn &((*tree)->a[i]);\n \t}\n+}\n \n-\t/*\n-\t * Did not find key at this (or any lower) level.\n-\t * Check if there's a matching subtree entry in tree->a[0].\n-\t * If so, unpack tree and resume search.\n-\t */\n-\tp = tree->a[0];\n-\tif (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE)\n-\t\treturn NULL;\n-\tl = (struct leaf_node *) CLR_PTR_TYPE(p);\n-\tif (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) {\n-\t\t/* unpack tree and resume search */\n-\t\ttree->a[0] = NULL;\n-\t\tload_subtree(l, tree, n);\n-\t\tfree(l);\n-\t\treturn note_tree_find(tree, n, key_sha1);\n+/*\n+ * To find a leaf_node:\n+ * Search to the tree location appropriate for the given key:\n+ * If a note entry with matching key, return the note entry, else return NULL.\n+ */\n+static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,\n+\t\tconst unsigned char *key_sha1)\n+{\n+\tvoid **p = note_tree_search(&tree, &n, key_sha1);\n+\tif (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) {\n+\t\tstruct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p);\n+\t\tif (!hashcmp(key_sha1, l->key_sha1))\n+\t\t\treturn l;\n \t}\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- * 1. Start at the root node, with n = 0\n- * 2. Use the nth nibble of the key as an index into a:\n- *    - If a[n] is NULL, store the tweaked pointer directly into a[n]\n- *    - If a[n] is an int_node, recurse into that node and increment n\n- *    - If a[n] is a leaf_node:\n- *      1. Check if they're equal, and handle that (abort? overwrite?)\n- *      2. Create a new int_node, and store both leaf_nodes there\n- *      3. Store the new int_node into a[n].\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+ * - 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+ *   location, and restart the insert operation from that level.\n+ * - Else, create a new int_node, holding both the node-at-location and the\n+ *   node-to-be-inserted, and store the new int_node into the location.\n  */\n-static int note_tree_insert(struct int_node *tree, unsigned char n,\n-\t\tconst struct leaf_node *entry, unsigned char type)\n+static void note_tree_insert(struct int_node *tree, unsigned char n,\n+\t\tstruct leaf_node *entry, unsigned char type)\n {\n \tstruct int_node *new_node;\n-\tconst struct leaf_node *l;\n-\tint ret;\n-\tunsigned char i = GET_NIBBLE(n, entry->key_sha1);\n-\tvoid *p = tree->a[i];\n-\tassert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL);\n-\tswitch(GET_PTR_TYPE(p)) {\n+\tstruct leaf_node *l;\n+\tvoid **p = note_tree_search(&tree, &n, entry->key_sha1);\n+\n+\tassert(GET_PTR_TYPE(entry) == 0); /* no type bits set */\n+\tl = (struct leaf_node *) CLR_PTR_TYPE(*p);\n+\tswitch(GET_PTR_TYPE(*p)) {\n \tcase PTR_TYPE_NULL:\n-\t\tassert(!p);\n-\t\ttree->a[i] = SET_PTR_TYPE(entry, type);\n-\t\treturn 0;\n-\tcase PTR_TYPE_INTERNAL:\n-\t\treturn note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type);\n-\tdefault:\n-\t\tassert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE ||\n-\t\t\tGET_PTR_TYPE(p) == PTR_TYPE_SUBTREE);\n-\t\tl = (const struct leaf_node *) CLR_PTR_TYPE(p);\n-\t\tif (!hashcmp(entry->key_sha1, l->key_sha1))\n-\t\t\treturn -1; /* abort insert on matching key */\n-\t\tnew_node = (struct int_node *)\n-\t\t\txcalloc(sizeof(struct int_node), 1);\n-\t\tret = note_tree_insert(new_node, n + 1,\n-\t\t\tCLR_PTR_TYPE(p), GET_PTR_TYPE(p));\n-\t\tif (ret) {\n-\t\t\tfree(new_node);\n-\t\t\treturn -1;\n+\t\tassert(!*p);\n+\t\t*p = SET_PTR_TYPE(entry, type);\n+\t\treturn;\n+\tcase PTR_TYPE_NOTE:\n+\t\tswitch (type) {\n+\t\tcase PTR_TYPE_NOTE:\n+\t\t\tif (!hashcmp(l->key_sha1, entry->key_sha1)) {\n+\t\t\t\t/* skip concatenation if l == entry */\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 commit %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+\t\t\t\tfree(entry);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase PTR_TYPE_SUBTREE:\n+\t\t\tif (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1,\n+\t\t\t\t\t\t    entry->key_sha1)) {\n+\t\t\t\t/* unpack 'entry' */\n+\t\t\t\tload_subtree(entry, tree, n);\n+\t\t\t\tfree(entry);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\t}\n+\t\tbreak;\n+\tcase PTR_TYPE_SUBTREE:\n+\t\tif (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) {\n+\t\t\t/* unpack 'l' and restart insert */\n+\t\t\t*p = NULL;\n+\t\t\tload_subtree(l, tree, n);\n+\t\t\tfree(l);\n+\t\t\tnote_tree_insert(tree, n, entry, type);\n+\t\t\treturn;\n \t\t}\n-\t\ttree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);\n-\t\treturn note_tree_insert(new_node, n + 1, entry, type);\n+\t\tbreak;\n \t}\n+\n+\t/* non-matching leaf_node */\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+\t*p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);\n+\tnote_tree_insert(new_node, n + 1, entry, type);\n }\n \n /* Free the entire notes data contained in the given tree */\n@@ -220,7 +301,6 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,\n {\n \tunsigned char commit_sha1[20];\n \tunsigned int prefix_len;\n-\tint status;\n \tvoid *buf;\n \tstruct tree_desc desc;\n \tstruct name_entry entry;\n@@ -254,8 +334,7 @@ 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\tstatus = note_tree_insert(node, n, l, type);\n-\t\t\tassert(!status);\n+\t\t\tnote_tree_insert(node, n, l, type);\n \t\t}\n \t}\n \tfree(buf);\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124462","messageId":"1255083738-23263-16-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 14/22] Add selftests verifying concatenation of multiple notes for the same commit","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:10Z","receivedAt":"2009-10-09T10:22:10Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Also verify that multiple references to the _same_ note blob are _not_\nconcatenated.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n t/t3303-notes-subtrees.sh |   84 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 84 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh\nindex cbb9d35..edc4bc8 100755\n--- a/t/t3303-notes-subtrees.sh\n+++ b/t/t3303-notes-subtrees.sh\n@@ -101,4 +101,88 @@ test_expect_success 'verify notes in 4/36-fanout' 'verify_notes'\n test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\"'\n test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'\n \n+test_same_notes () {\n+\t(\n+\t\tstart_note_commit &&\n+\t\tnr=$number_of_commits &&\n+\t\tgit rev-list refs/heads/master |\n+\t\twhile read sha1; do\n+\t\t\tfirst_note_path=$(echo \"$sha1\" | sed \"$1\")\n+\t\t\tsecond_note_path=$(echo \"$sha1\" | sed \"$2\")\n+\t\t\tcat <<INPUT_END &&\n+M 100644 inline $second_note_path\n+data <<EOF\n+note for commit #$nr\n+EOF\n+\n+M 100644 inline $first_note_path\n+data <<EOF\n+note for commit #$nr\n+EOF\n+\n+INPUT_END\n+\n+\t\t\tnr=$(($nr-1))\n+\t\tdone\n+\t) |\n+\tgit fast-import --quiet\n+}\n+\n+test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes \"s|^..|&/|\" \"s|^....|&/|\"'\n+test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes'\n+\n+test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^..|&/|\"'\n+test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes'\n+\n+test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^....|&/|\"'\n+test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes'\n+\n+test_concatenated_notes () {\n+\t(\n+\t\tstart_note_commit &&\n+\t\tnr=$number_of_commits &&\n+\t\tgit rev-list refs/heads/master |\n+\t\twhile read sha1; do\n+\t\t\tfirst_note_path=$(echo \"$sha1\" | sed \"$1\")\n+\t\t\tsecond_note_path=$(echo \"$sha1\" | sed \"$2\")\n+\t\t\tcat <<INPUT_END &&\n+M 100644 inline $second_note_path\n+data <<EOF\n+second note for commit #$nr\n+EOF\n+\n+M 100644 inline $first_note_path\n+data <<EOF\n+first note for commit #$nr\n+EOF\n+\n+INPUT_END\n+\n+\t\t\tnr=$(($nr-1))\n+\t\tdone\n+\t) |\n+\tgit fast-import --quiet\n+}\n+\n+verify_concatenated_notes () {\n+    git log | grep \"^    \" > output &&\n+    i=$number_of_commits &&\n+    while [ $i -gt 0 ]; do\n+        echo \"    commit #$i\" &&\n+        echo \"    first note for commit #$i\" &&\n+        echo \"    second note for commit #$i\" &&\n+        i=$(($i-1));\n+    done > expect &&\n+    test_cmp expect output\n+}\n+\n+test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes \"s|^..|&/|\" \"s|^....|&/|\"'\n+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes'\n+\n+test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^..|&/|\"'\n+test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'\n+\n+test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes \"s|^\\(..\\)\\(..\\)|\\1/\\2/|\" \"s|^....|&/|\"'\n+test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes'\n+\n test_done\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124476","messageId":"1255083738-23263-17-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 15/22] Notes API: get_commit_notes() -> format_note() + remove the commit restriction","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:11Z","receivedAt":"2009-10-09T10:22:11Z","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 7f350bb..81791f5 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -703,8 +703,8 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,\n \t\tformat_decoration(sb, commit);\n \t\treturn 1;\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@@ -982,8 +982,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":"124461","messageId":"1255083738-23263-18-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 16/22] Notes API: init_notes(): Initialize the notes tree from the given notes ref","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:12Z","receivedAt":"2009-10-09T10:22:12Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Created by a simple refactoring of initialize_notes().\n\nAlso add a new 'flags' parameter, which is a bitwise combination of notes\ninitialization flags. For now, there is only one flag - NOTES_INIT_EMPTY -\nwhich indicates that the notes tree should not auto-load the contents of\nthe given (or default) notes ref, but rather should leave the notes tree\ninitialized to an empty state. This will become useful in the future when\nmanipulating the notes tree through the notes API.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   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":"124477","messageId":"1255083738-23263-19-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 17/22] Notes API: add_note(): Add note objects to the internal notes tree structure","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:13Z","receivedAt":"2009-10-09T10:22:13Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"Signed-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |   11 +++++++++++\n notes.h |    4 ++++\n 2 files changed, 15 insertions(+), 0 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 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":"124475","messageId":"1255083738-23263-20-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 18/22] Notes API: get_note(): Return the note annotating the given object","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:14Z","receivedAt":"2009-10-09T10:22:14Z","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":"124474","messageId":"1255083738-23263-21-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 19/22] Notes API: for_each_note(): Traverse the entire notes tree with a callback","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:15Z","receivedAt":"2009-10-09T10:22:15Z","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 created on-the-fly, while traversing).\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n notes.c |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n notes.h |    9 +++++\n 2 files changed, 110 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..28648fd 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -28,6 +28,15 @@ 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+ * Calls the specified function for each note until it returns nonzero,\n+ * and returns the value\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":"124467","messageId":"1255083738-23263-22-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 20/22] Notes API: Allow multiple concurrent notes trees with new struct notes_tree","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:16Z","receivedAt":"2009-10-09T10:22:16Z","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  |   57 +++++++++++++++++++++++++++++++++++++--------------\n pretty.c |    4 +-\n 3 files changed, 85 insertions(+), 43 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 28648fd..181c3d6 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,35 +25,43 @@\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- * Calls the specified function for each note until it returns nonzero,\n- * and returns the value\n+ * Calls the specified 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  */\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@@ -47,12 +70,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 81791f5..5ceb702 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -703,7 +703,7 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,\n \t\tformat_decoration(sb, commit);\n \t\treturn 1;\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@@ -982,7 +982,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":"124464","messageId":"1255083738-23263-23-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 21/22] Refactor notes concatenation into a flexible interface for combining notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:17Z","receivedAt":"2009-10-09T10:22:17Z","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 181c3d6..f5d4ccd 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":"124469","messageId":"1255083738-23263-24-git-send-email-johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-1-git-send-email-johan@herland.net","subject":"[RFC/PATCHv7 22/22] fast-import: Proper notes tree manipulation using the notes API","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:22:18Z","receivedAt":"2009-10-09T10:22:18Z","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. Note\nobjects are added to the notes tree through the 'N' command, and when\nwe're about to store the tree object for the current commit, we walk\nthrough the notes tree and insert all the notes into the stored tree.\n\nSigned-off-by: Johan Herland <johan@herland.net>\n---\n fast-import.c          |   98 ++++++++++++++++++++++++++++--\n t/t9300-fast-import.sh |  156 ++++++++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 235 insertions(+), 19 deletions(-)\n\ndiff --git a/fast-import.c b/fast-import.c\nindex fcdcfaa..5837875 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,11 @@ struct recent_command\n \tchar *buf;\n };\n \n+struct notes_tree_list {\n+\tstruct notes_tree tree;\n+\tstruct notes_tree_list *next;\n+};\n+\n /* Configured limits on output */\n static unsigned long max_depth = 10;\n static off_t max_packsize = (1LL << 32) - 1;\n@@ -345,6 +352,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@@ -2060,7 +2070,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@@ -2130,8 +2140,8 @@ static void note_change_n(struct branch *b)\n \t\t\t    typename(type), command_buf.buf);\n \t}\n \n-\ttree_content_set(&b->branch_tree, sha1_to_hex(commit_sha1), sha1,\n-\t\tS_IFREG | 0644, NULL);\n+\tassert(notes);\n+\tadd_note(notes, commit_sha1, sha1, NULL);\n }\n \n static void file_change_deleteall(struct branch *b)\n@@ -2254,6 +2264,68 @@ static struct hash_list *parse_merge(unsigned int *count)\n \treturn list;\n }\n \n+static struct notes_tree *new_notes_tree(struct branch *b)\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, b->name, combine_notes_overwrite, NOTES_INIT_EMPTY);\n+\tret->next = notes_trees;\n+\tnotes_trees = ret;\n+\tb->has_notes = 1;\n+\treturn &ret->tree;\n+}\n+\n+static struct notes_tree *get_notes_tree(struct branch *b)\n+{\n+\tstruct notes_tree_list *cur = notes_trees;\n+\tif (!b->has_notes)\n+\t\treturn NULL;\n+\twhile (cur && strcmp(cur->tree.ref, b->name))\n+\t\tcur = cur->next;\n+\tassert(cur);\n+\treturn &cur->tree;\n+}\n+\n+static void delete_notes_tree(struct branch *b, struct notes_tree **tree)\n+{\n+\tstruct notes_tree_list *cur = notes_trees, *prev = NULL;\n+\twhile (cur && strcmp(cur->tree.ref, b->name)) {\n+\t\tprev = cur;\n+\t\tcur = cur->next;\n+\t}\n+\tassert(cur && &cur->tree == *tree);\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+\t*tree = NULL;\n+\tb->has_notes = 0;\n+}\n+\n+static int write_notes_set_helper(\n+\tconst unsigned char *object_sha1,\n+\tconst unsigned char *note_sha1,\n+\tconst char *note_tree_path,\n+\tvoid *cb_data)\n+{\n+\tstruct tree_entry *t = (struct tree_entry *) cb_data;\n+\ttree_content_set(t, note_tree_path, note_sha1, S_IFREG | 0644, NULL);\n+\treturn 0;\n+}\n+\n+static int write_notes_remove_helper(\n+\tconst unsigned char *object_sha1,\n+\tconst unsigned char *note_sha1,\n+\tconst char *note_tree_path,\n+\tvoid *cb_data)\n+{\n+\tstruct tree_entry *t = (struct tree_entry *) cb_data;\n+\ttree_content_remove(t, note_tree_path, NULL);\n+\treturn 0;\n+}\n+\n static void parse_new_commit(void)\n {\n \tstatic struct strbuf msg = STRBUF_INIT;\n@@ -2263,6 +2335,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;\n \n \t/* Obtain the branch name from the rest of our command */\n \tsp = strchr(command_buf.buf, ' ') + 1;\n@@ -2293,6 +2366,11 @@ static void parse_new_commit(void)\n \t\tload_branch(b);\n \t}\n \n+\t/* Retrieve notes tree, if needed */\n+\tnotes = get_notes_tree(b);\n+\tif (notes)\n+\t\tfor_each_note(notes, write_notes_remove_helper, &b->branch_tree);\n+\n \t/* file_change* */\n \twhile (command_buf.len > 0) {\n \t\tif (!prefixcmp(command_buf.buf, \"M \"))\n@@ -2303,10 +2381,16 @@ 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 = new_notes_tree(b);\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\tdelete_notes_tree(b, &notes);\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@@ -2316,6 +2400,8 @@ static void parse_new_commit(void)\n \t}\n \n \t/* build the tree and the commit */\n+\tif (notes)\n+\t\tfor_each_note(notes, write_notes_set_helper, &b->branch_tree);\n \tstore_tree(&b->branch_tree);\n \thashcpy(b->branch_tree.versions[0].sha1,\n \t\tb->branch_tree.versions[1].sha1);\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 2f5c323..50a2b8a 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,24 +1276,113 @@ 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 ###\n ### series R (feature and option)\n-- \n1.6.4.304.g1365c.dirty\n"},{"id":"124478","messageId":"200910091232.23209.johan@herland.net","threadId":"21168","inReplyTo":"1255083738-23263-2-git-send-email-johan@herland.net","subject":"Re: [RFC/PATCHv7 00/22] git notes","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-10-09T10:32:23Z","receivedAt":"2009-10-09T10:32:23Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Friday 09 October 2009, Johan Herland wrote:\n> Hi,\n> \n> Here is the 7th iteration of the git-notes series. Changes in this\n> iteration are as follows:\n\nOops... Diregard this one. Seems I had a stray 0000-*.patch.bak lying \naround...\n\n\n...Johan\n"},{"id":"124488","messageId":"20091009142524.GQ9261@spearce.org","threadId":"21168","inReplyTo":"1255083738-23263-24-git-send-email-johan@herland.net","subject":"Re: [RFC/PATCHv7 22/22] fast-import: Proper notes tree manipulation using the notes API","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-10-09T14:25:24Z","receivedAt":"2009-10-09T14:25:24Z","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> the manipulation of note objects through a fast-import stream. Note\n> objects are added to the notes tree through the 'N' command, and when\n> we're about to store the tree object for the current commit, we walk\n> through the notes tree and insert all the notes into the stored tree.\n\nSome high level comments about this patch:\n\n- You don't destroy the struct notes_tree during unload_one_branch()\n  which means notes trees stay in memory even if the branch table\n  is overflowing.  I think you should discard the notes tree when\n  a branch unloads, and recreate it when the branch loads.\n\n- Destroying and adding back all notes is OK with ~20k notes, but\n  doing that with ~150k-~800k notes is going to slow down a lot,\n  losing the \"fast\" part.\n\n-- \nShawn.\n"},{"id":"127964","messageId":"200911200243.18476.johan@herland.net","threadId":"21168","inReplyTo":"20091009142524.GQ9261@spearce.org","subject":"Re: [RFC/PATCHv7 22/22] fast-import: Proper notes tree manipulation using the notes API","fromName":"Johan Herland","fromEmail":"johan@herland.net","sentAt":"2009-11-20T01:43:18Z","receivedAt":"2009-11-20T01:43:18Z","isPatch":false,"sender":{"key":"johan@herland.net","avatar":"https://avatars.githubusercontent.com/u/547031?v=4"},"body":"On Friday 09 October 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 organize\n> > the manipulation of note objects through a fast-import stream. Note\n> > objects are added to the notes tree through the 'N' command, and when\n> > we're about to store the tree object for the current commit, we walk\n> > through the notes tree and insert all the notes into the stored tree.\n> \n> Some high level comments about this patch:\n> \n> - You don't destroy the struct notes_tree during unload_one_branch()\n>   which means notes trees stay in memory even if the branch table\n>   is overflowing.  I think you should discard the notes tree when\n>   a branch unloads, and recreate it when the branch loads.\n> \n> - Destroying and adding back all notes is OK with ~20k notes, but\n>   doing that with ~150k-~800k notes is going to slow down a lot,\n>   losing the \"fast\" part.\n\nThanks for the comments. I've tried to address them in the 8th iteration of \nthis series (Patch 8/10 to be more precise), just submitted to the mailing \nlist.\n\n\n...Johan\n\n-- \nJohan Herland, <johan@herland.net>\nwww.herland.net\n"},{"id":"160101","messageId":"20110131183350.GB31826@burratino","threadId":"21168","inReplyTo":"1255083738-23263-8-git-send-email-johan@herland.net","subject":"[RFC] fast-import: notemodify (N) command","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-01-31T18:33:50Z","receivedAt":"2011-01-31T18:33:50Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Dear fast importers,\n\nAnother week, another fast-import protocol extension.\n\nMost DVCSes do not allow one to non-disruptively change the log\nmessage for a commit.  But sometimes people want to attach information to a\ncommit after the fact:\n\n - whether it was tested and worked correctly\n - who liked or disliked the commit (Acked-by, Reviewed-by)\n - corresponding revision number after export to another version\n   control system\n - bug number\n - corresponding compiled binary\n\nThe N command allows such notes to be attached to commits, like so:\n\n 1. first the commit is imported as usual (let's say it's \":1\").\n 2. commit annotations are added separately, like so:\n\n\tcommit refs/notes/commits\n\tcommitter A. U. Thor <author@example.com> Mon, 31 Jan 2011 12:15:59 -0600\n\tdata <<END\n\tNotes after review.\n\tEND\n\n\tN inline :1\n\tdata <<END\n\tAcked-by: me\n\tEND\n\nDetails:\n\n - there can be multiple categories of notes: \"refs/notes/commits\"\n   contains ordinary addenda to the commit message, but one might also\n   see refs/notes/bugzilla, refs/notes/svn-commit, and so on.\n\n - each commit gets at most one blob of notes in each category.  Later\n   notemodify (N) commands overwrite the effect from earlier ones.\n\n - the syntax of a notemodify command is as follows:\n\n\t'N' sp <dataref> sp <committish> lf\n\n   The <dataref> represents a blob with the annotations to be used\n   (\"inline\" is allowed, too, just like with filemodify).  The\n   <committish> can be any expression allowed in a 'from' command\n   (branch name, mark reference :<idnum>, other commit name) and\n   represents the commit that is to be annotated.\n\n - this has been supported in git since v1.6.6.  There is no\n   \"feature\" for it --- I don't think the feature declaration\n   facility existed yet.\n\nDo other DVCSes support something like this?  Should it get a\nfeature name?\n\nJonathan\n"},{"id":"160103","messageId":"AANLkTi=3P0xc3mJj5Tsg_P26SAXmgVfCk1VeYQ=k2UtT@mail.gmail.com","threadId":"21168","inReplyTo":"20110131183350.GB31826@burratino","subject":"Re: [Vcs-fast-import-devs] [RFC] fast-import: notemodify (N) command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2011-01-31T18:48:23Z","receivedAt":"2011-01-31T18:48:23Z","isPatch":false,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Jan 31, 2011 at 19:33, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Most DVCSes do not allow one to non-disruptively change the log\n> message for a commit.  But sometimes people want to attach information to a\n> commit after the fact:\n>\n>  - whether it was tested and worked correctly\n>  - who liked or disliked the commit (Acked-by, Reviewed-by)\n>  - corresponding revision number after export to another version\n>   control system\n>  - bug number\n>  - corresponding compiled binary\n\nI talked with Augie Fackler (from hg) about this on IM and he says:\n> We don't support anything like that at present (no demand, when we check\n> nobody really seems to use git notes for anything)\n> so it doesn't seem relevant in fast-export\n\nSo at least HG doesn't (currently) have any functionality that they\ncould use to implement the importing of such a stream.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"160104","messageId":"20110131190128.GC32374@burratino","threadId":"21168","inReplyTo":"AANLkTi=3P0xc3mJj5Tsg_P26SAXmgVfCk1VeYQ=k2UtT@mail.gmail.com","subject":"Re: [RFC] fast-import: notemodify (N) command","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-01-31T19:01:28Z","receivedAt":"2011-01-31T19:01:28Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Sverre Rabbelier wrote:\n> I talked with Augie Fackler (from hg) about this on IM and he says:\n\n>> We don't support anything like that at present (no demand, when we check\n>> nobody really seems to use git notes for anything)\n>> so it doesn't seem relevant in fast-export\n>\n> So at least HG doesn't (currently) have any functionality that they\n> could use to implement the importing of such a stream.\n\nThanks, good to know.  I suppose this definitely needs a feature name,\nthen (I'll send a patch to make it \"feature notes\").\n\nJonathan\n\n[Aside: I suspect part of the reason \"git notes\" adoption is not so\ngreat is the lack of git notes fetch/git notes push.  Sample size\nof 1: I use notes heavily as a consumer, to dig up mailing list\nthreads[1], but have put off sharing my own annotations until I can\nfigure out how to make it convenient for others to use.]\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/109074/focus=109542\n"},{"id":"160120","messageId":"AANLkTikxFSa1CSujk2zgh4K8SWCuKHRrDRFVNt9JPGo0@mail.gmail.com","threadId":"21168","inReplyTo":"20110131190128.GC32374@burratino","subject":"Re: [RFC] fast-import: notemodify (N) command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2011-01-31T21:19:14Z","receivedAt":"2011-01-31T21:19:14Z","isPatch":false,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Jan 31, 2011 at 20:01, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Thanks, good to know.  I suppose this definitely needs a feature name,\n> then (I'll send a patch to make it \"feature notes\").\n\nSGTM.\n\n> [Aside: I suspect part of the reason \"git notes\" adoption is not so\n> great is the lack of git notes fetch/git notes push.  Sample size\n> of 1: I use notes heavily as a consumer, to dig up mailing list\n> threads[1], but have put off sharing my own annotations until I can\n> figure out how to make it convenient for others to use.]\n\nThat's another thing Augie mentioned that he (and I guess the hg\ncommunity at large) dislikes, the fact that they're not propagated.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"160132","messageId":"4D4739B5.6080103@vilain.net","threadId":"21168","inReplyTo":"AANLkTikxFSa1CSujk2zgh4K8SWCuKHRrDRFVNt9JPGo0@mail.gmail.com","subject":"Re: [RFC] fast-import: notemodify (N) command","fromName":"Sam Vilain","fromEmail":"sam@vilain.net","sentAt":"2011-01-31T22:37:41Z","receivedAt":"2011-01-31T22:37:41Z","isPatch":false,"sender":{"key":"sam@vilain.net","avatar":"https://gravatar.com/avatar/8fc840ca854dbf6f7065b4335e3b934951c1dca3b11db688e95e471901f8f4a8?d=mp&s=160"},"body":"On 01/02/11 10:19, Sverre Rabbelier wrote:\n> That's another thing Augie mentioned that he (and I guess the hg\n> community at large) dislikes, the fact that they're not propagated.\n\nThis is not a \"fact\".\n\nIf you add configuration in your git config to fetch and push the refs,\nthen they are propagated.\n\nJust because you disagree with the current interface or defaults doesn't\nmean the design is wrong.  I hear the same arguments against submodules,\nwhich is a shame because the message isn't getting through to people\nthat the porcelain can and should be extended to make people's lives\neasier.  It's just that instead of second-guessing what shape they\nshould take, the design and plumbing are written so that people can\nwrite scripts to make it work.\n\nIt's a slower path, but you end up with a better tool at the end of it.\n\nSam\n"},{"id":"160141","messageId":"AANLkTikz-3euie78rDAO=QVLwoW-r36T5zfmSVQ14Y-k@mail.gmail.com","threadId":"21168","inReplyTo":"4D4739B5.6080103@vilain.net","subject":"Re: [RFC] fast-import: notemodify (N) command","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2011-02-01T00:13:06Z","receivedAt":"2011-02-01T00:13:06Z","isPatch":false,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Mon, Jan 31, 2011 at 23:37, Sam Vilain <sam@vilain.net> wrote:\n> This is not a \"fact\".\n>\n> If you add configuration in your git config to fetch and push the refs,\n> then they are propagated.\n\nHeh, I was contemplating whether to add \"(by default)\" or not, I guess\nI should have.\n\n-- \nCheers,\n\nSverre Rabbelier\n"}]}