From 879ef2485d6ced20845ca626ecb45a9b65aa3a70 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 20 Dec 2008 13:05:14 +0100 Subject: [PATCH 1/6] Introduce commit notes Commit notes are blobs which are shown together with the commit message. These blobs are taken from the notes ref, which you can configure by the config variable core.notesRef, which in turn can be overridden by the environment variable GIT_NOTES_REF. The notes ref is a branch which contains "files" whose names are the names of the corresponding commits (i.e. the SHA-1). The rationale for putting this information into a ref is this: we want to be able to fetch and possibly union-merge the notes, maybe even look at the date when a note was introduced, and we want to store them efficiently together with the other objects. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 15 +++++++++ Makefile | 2 ++ cache.h | 3 ++ commit.c | 1 + config.c | 5 +++ environment.c | 1 + notes.c | 68 ++++++++++++++++++++++++++++++++++++++++ notes.h | 7 +++++ pretty.c | 5 +++ 9 files changed, 107 insertions(+) create mode 100644 notes.c create mode 100644 notes.h diff --git a/Documentation/config.txt b/Documentation/config.txt index 21ea16590b..b35a32abe1 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -422,6 +422,21 @@ relatively high IO latencies. With this set to 'true', git will do the index comparison to the filesystem data in parallel, allowing overlapping IO's. +core.notesRef:: + When showing commit messages, also show notes which are stored in + the given ref. This ref is expected to contain paths of the form + ??/*, where the directory name consists of the first two + characters of the commit name, and the base name consists of + the remaining 38 characters. ++ +If such a path exists in the given ref, the referenced blob is read, and +appended to the commit message, separated by a "Notes:" line. If the +given ref itself does not exist, it is not an error, but means that no +notes should be print. ++ +This setting defaults to "refs/notes/commits", and can be overridden by +the `GIT_NOTES_REF` environment variable. + alias.*:: Command aliases for the linkgit:git[1] command wrapper - e.g. after defining "alias.last = cat-file commit HEAD", the invocation diff --git a/Makefile b/Makefile index aabf0130b9..0bc96d8d32 100644 --- a/Makefile +++ b/Makefile @@ -370,6 +370,7 @@ LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h LIB_H += merge-recursive.h +LIB_H += notes.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@ -451,6 +452,7 @@ LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o +LIB_OBJS += notes.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o diff --git a/cache.h b/cache.h index 231c06d772..6158d5546b 100644 --- a/cache.h +++ b/cache.h @@ -367,6 +367,8 @@ static inline enum object_type object_type(unsigned int mode) #define GITATTRIBUTES_FILE ".gitattributes" #define INFOATTRIBUTES_FILE "info/attributes" #define ATTRIBUTE_MACRO_PREFIX "[attr]" +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF" +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" extern int is_bare_repository_cfg; extern int is_bare_repository(void); @@ -538,6 +540,7 @@ enum rebase_setup_type { extern enum branch_track git_branch_track; extern enum rebase_setup_type autorebase; +extern char *notes_ref_name; #define GIT_REPO_VERSION 0 extern int repository_format_version; diff --git a/commit.c b/commit.c index c99db162a4..10e532afe1 100644 --- a/commit.c +++ b/commit.c @@ -5,6 +5,7 @@ #include "utf8.h" #include "diff.h" #include "revision.h" +#include "notes.h" int save_commit_buffer = 1; diff --git a/config.c b/config.c index 790405a213..e5d5b4bd06 100644 --- a/config.c +++ b/config.c @@ -469,6 +469,11 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.notesref")) { + notes_ref_name = xstrdup(value); + return 0; + } + if (!strcmp(var, "core.pager")) return git_config_string(&pager_program, var, value); diff --git a/environment.c b/environment.c index e278bce0ea..0edae21e74 100644 --- a/environment.c +++ b/environment.c @@ -45,6 +45,7 @@ enum rebase_setup_type autorebase = AUTOREBASE_NEVER; /* Parallel index stat data preload? */ int core_preload_index = 0; +char *notes_ref_name; /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; diff --git a/notes.c b/notes.c new file mode 100644 index 0000000000..91ec77f3f0 --- /dev/null +++ b/notes.c @@ -0,0 +1,68 @@ +#include "cache.h" +#include "commit.h" +#include "notes.h" +#include "refs.h" +#include "utf8.h" +#include "strbuf.h" + +static int initialized; + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding) +{ + static const char *utf8 = "utf-8"; + struct strbuf name = STRBUF_INIT; + const char *hex; + unsigned char sha1[20]; + char *msg; + unsigned long msgoffset, msglen; + enum object_type type; + + if (!initialized) { + const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (env) + notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); + else if (!notes_ref_name) + notes_ref_name = GIT_NOTES_DEFAULT_REF; + if (notes_ref_name && read_ref(notes_ref_name, sha1)) + notes_ref_name = NULL; + initialized = 1; + } + + if (!notes_ref_name) + return; + + strbuf_addf(&name, "%s:%s", notes_ref_name, + sha1_to_hex(commit->object.sha1)); + if (get_sha1(name.buf, sha1)) + return; + + if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || + type != OBJ_BLOB) + return; + + if (output_encoding && *output_encoding && + strcmp(utf8, output_encoding)) { + char *reencoded = reencode_string(msg, output_encoding, utf8); + if (reencoded) { + free(msg); + msg = reencoded; + msglen = strlen(msg); + } + } + + /* we will end the annotation by a newline anyway */ + if (msglen && msg[msglen - 1] == '\n') + msglen--; + + strbuf_addstr(sb, "\nNotes:\n"); + + for (msgoffset = 0; msgoffset < msglen;) { + int linelen = strchrnul(msg, '\n') - msg; + + strbuf_addstr(sb, " "); + strbuf_add(sb, msg + msgoffset, linelen); + msgoffset += linelen; + } + free(msg); +} diff --git a/notes.h b/notes.h new file mode 100644 index 0000000000..79d21b65f5 --- /dev/null +++ b/notes.h @@ -0,0 +1,7 @@ +#ifndef NOTES_H +#define NOTES_H + +void get_commit_notes(const struct commit *commit, struct strbuf *sb, + const char *output_encoding); + +#endif diff --git a/pretty.c b/pretty.c index f6ff31264b..2d2872f3b5 100644 --- a/pretty.c +++ b/pretty.c @@ -6,6 +6,7 @@ #include "string-list.h" #include "mailmap.h" #include "log-tree.h" +#include "notes.h" static char *user_format; @@ -881,5 +882,9 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, */ if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body) strbuf_addch(sb, '\n'); + + if (fmt != CMIT_FMT_ONELINE) + get_commit_notes(commit, sb, encoding); + free(reencoded); } From 055a5975258f37eecdfcf609a472ab4957a59263 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 20 Dec 2008 13:05:33 +0100 Subject: [PATCH 2/6] Add a script to edit/inspect notes The script 'git notes' allows you to edit and show commit notes, by calling either git notes show or git notes edit Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- .gitignore | 1 + Documentation/git-notes.txt | 46 ++++++++++++++++++++++++++ Makefile | 1 + command-list.txt | 1 + git-notes.sh | 65 +++++++++++++++++++++++++++++++++++++ t/t3301-notes.sh | 65 +++++++++++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 Documentation/git-notes.txt create mode 100755 git-notes.sh create mode 100755 t/t3301-notes.sh diff --git a/.gitignore b/.gitignore index d9adce585a..f89428b968 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ git-mktag git-mktree git-name-rev git-mv +git-notes git-pack-redundant git-pack-objects git-pack-refs diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt new file mode 100644 index 0000000000..3d93625f9a --- /dev/null +++ b/Documentation/git-notes.txt @@ -0,0 +1,46 @@ +git-notes(1) +============ + +NAME +---- +git-notes - Add/inspect commit notes + +SYNOPSIS +-------- +[verse] +'git-notes' (edit | show) [commit] + +DESCRIPTION +----------- +This command allows you to add notes to commit messages, without +changing the commit. To discern these notes from the message stored +in the commit object, the notes are indented like the message, after +an unindented line saying "Notes:". + +To disable commit notes, you have to set the config variable +core.notesRef to the empty string. Alternatively, you can set it +to a different ref, something like "refs/notes/bugzilla". This setting +can be overridden by the environment variable "GIT_NOTES_REF". + + +SUBCOMMANDS +----------- + +edit:: + Edit the notes for a given commit (defaults to HEAD). + +show:: + Show the notes for a given commit (defaults to HEAD). + + +Author +------ +Written by Johannes Schindelin + +Documentation +------------- +Documentation by Johannes Schindelin + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Makefile b/Makefile index 0bc96d8d32..69e1073e3e 100644 --- a/Makefile +++ b/Makefile @@ -258,6 +258,7 @@ SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh +SCRIPT_SH += git-notes.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh diff --git a/command-list.txt b/command-list.txt index 3583a33ee9..2dc2c3320c 100644 --- a/command-list.txt +++ b/command-list.txt @@ -73,6 +73,7 @@ git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain common git-name-rev plumbinginterrogators +git-notes mainporcelain git-pack-objects plumbingmanipulators git-pack-redundant plumbinginterrogators git-pack-refs ancillarymanipulators diff --git a/git-notes.sh b/git-notes.sh new file mode 100755 index 0000000000..bfdbaa8527 --- /dev/null +++ b/git-notes.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +USAGE="(edit | show) [commit]" +. git-sh-setup + +test -n "$3" && usage + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +MESSAGE="$GIT_DIR"/new-notes-$COMMIT +trap ' + test -f "$MESSAGE" && rm "$MESSAGE" +' 0 + +case "$ACTION" in +edit) + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MESSAGE" + + GIT_INDEX_FILE="$MESSAGE".idx + export GIT_INDEX_FILE + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + git cat-file blob :$COMMIT >> "$MESSAGE" 2> /dev/null + fi + + ${VISUAL:-${EDITOR:-vi}} "$MESSAGE" + + grep -v ^# < "$MESSAGE" | git stripspace > "$MESSAGE".processed + mv "$MESSAGE".processed "$MESSAGE" + if [ -s "$MESSAGE" ]; then + BLOB=$(git hash-object -w "$MESSAGE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh new file mode 100755 index 0000000000..ba42c45ec4 --- /dev/null +++ b/t/t3301-notes.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes' + +. ./test-lib.sh + +cat > fake_editor.sh << \EOF +echo "$MSG" > "$1" +echo "$MSG" >& 2 +EOF +chmod a+x fake_editor.sh +VISUAL=./fake_editor.sh +export VISUAL + +test_expect_success 'cannot annotate non-existing HEAD' ' + ! MSG=3 git notes edit +' + +test_expect_success setup ' + : > a1 && + git add a1 && + test_tick && + git commit -m 1st && + : > a2 && + git add a2 && + test_tick && + git commit -m 2nd +' + +test_expect_success 'need valid notes ref' ' + ! MSG=1 GIT_NOTES_REF='/' git notes edit && + ! MSG=2 GIT_NOTES_REF='/' git notes show +' + +test_expect_success 'create notes' ' + git config core.notesRef refs/notes/commits && + MSG=b1 git notes edit && + test ! -f .git/new-notes && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b1 = $(git notes show) && + git show HEAD^ && + ! git notes show HEAD^ +' + +cat > expect << EOF +commit 268048bfb8a1fb38e703baceb8ab235421bf80c5 +Author: A U Thor +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + b1 +EOF + +test_expect_success 'show notes' ' + ! (git cat-file commit HEAD | grep b1) && + git log -1 > output && + git diff expect output +' + +test_done From 2dd625d022074bb677bdd5caa5146cabaf726123 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 20 Dec 2008 13:05:47 +0100 Subject: [PATCH 3/6] Speed up git notes lookup To avoid looking up each and every commit in the notes ref's tree object, which is very expensive, speed things up by slurping the tree object's contents into a hash_map. The idea fo the hashmap singleton is from David Reiss, initial benchmarking by Jeff King. Note: the implementation allows for arbitrary entries in the notes tree object, ignoring those that do not reference a valid object. This allows you to annotate arbitrary branches, or objects. [jc: fixed an obvious error in initialize_hash_map()] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- notes.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/notes.c b/notes.c index 91ec77f3f0..ad43a2ed15 100644 --- a/notes.c +++ b/notes.c @@ -4,16 +4,112 @@ #include "refs.h" #include "utf8.h" #include "strbuf.h" +#include "tree-walk.h" + +struct entry { + unsigned char commit_sha1[20]; + unsigned char notes_sha1[20]; +}; + +struct hash_map { + struct entry *entries; + off_t count, size; +}; static int initialized; +static struct hash_map hash_map; + +static int hash_index(struct hash_map *map, const unsigned char *sha1) +{ + int i = ((*(unsigned int *)sha1) % map->size); + + for (;;) { + unsigned char *current = map->entries[i].commit_sha1; + + if (!hashcmp(sha1, current)) + return i; + + if (is_null_sha1(current)) + return -1 - i; + + if (++i == map->size) + i = 0; + } +} + +static void add_entry(const unsigned char *commit_sha1, + const unsigned char *notes_sha1) +{ + int index; + + if (hash_map.count + 1 > hash_map.size >> 1) { + int i, old_size = hash_map.size; + struct entry *old = hash_map.entries; + + hash_map.size = old_size ? old_size << 1 : 64; + hash_map.entries = (struct entry *) + xcalloc(sizeof(struct entry), hash_map.size); + + for (i = 0; i < old_size; i++) + if (!is_null_sha1(old[i].commit_sha1)) { + index = -1 - hash_index(&hash_map, + old[i].commit_sha1); + memcpy(hash_map.entries + index, old + i, + sizeof(struct entry)); + } + free(old); + } + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) { + index = -1 - index; + hash_map.count++; + } + + hashcpy(hash_map.entries[index].commit_sha1, commit_sha1); + hashcpy(hash_map.entries[index].notes_sha1, notes_sha1); +} + +static void initialize_hash_map(const char *notes_ref_name) +{ + unsigned char sha1[20], commit_sha1[20]; + unsigned mode; + struct tree_desc desc; + struct name_entry entry; + void *buf; + + if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || + get_tree_entry(commit_sha1, "", sha1, &mode)) + return; + + buf = fill_tree_descriptor(&desc, sha1); + if (!buf) + die("Could not read %s for notes-index", sha1_to_hex(sha1)); + + while (tree_entry(&desc, &entry)) + if (!get_sha1(entry.path, commit_sha1)) + add_entry(commit_sha1, entry.sha1); + free(buf); +} + +static unsigned char *lookup_notes(const unsigned char *commit_sha1) +{ + int index; + + if (!hash_map.size) + return NULL; + + index = hash_index(&hash_map, commit_sha1); + if (index < 0) + return NULL; + return hash_map.entries[index].notes_sha1; +} void get_commit_notes(const struct commit *commit, struct strbuf *sb, const char *output_encoding) { static const char *utf8 = "utf-8"; - struct strbuf name = STRBUF_INIT; - const char *hex; - unsigned char sha1[20]; + unsigned char *sha1; char *msg; unsigned long msgoffset, msglen; enum object_type type; @@ -24,17 +120,12 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); else if (!notes_ref_name) notes_ref_name = GIT_NOTES_DEFAULT_REF; - if (notes_ref_name && read_ref(notes_ref_name, sha1)) - notes_ref_name = NULL; + initialize_hash_map(notes_ref_name); initialized = 1; } - if (!notes_ref_name) - return; - - strbuf_addf(&name, "%s:%s", notes_ref_name, - sha1_to_hex(commit->object.sha1)); - if (get_sha1(name.buf, sha1)) + sha1 = lookup_notes(commit->object.sha1); + if (!sha1) return; if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || From 009318b1f1db24588d93bc658ea6c6a5324c9c3c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 20 Dec 2008 13:06:03 +0100 Subject: [PATCH 4/6] Add an expensive test for git-notes git-notes have the potential of being pretty expensive, so test with a lot of commits. A lot. So to make things cheaper, you have to opt-in explicitely, by setting the environment variable GIT_NOTES_TIMING_TESTS. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t3302-notes-index-expensive.sh | 98 ++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100755 t/t3302-notes-index-expensive.sh diff --git a/t/t3302-notes-index-expensive.sh b/t/t3302-notes-index-expensive.sh new file mode 100755 index 0000000000..00d27bf6ef --- /dev/null +++ b/t/t3302-notes-index-expensive.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='Test commit notes index (expensive!)' + +. ./test-lib.sh + +test -z "$GIT_NOTES_TIMING_TESTS" && { + say Skipping timing tests + test_done + exit +} + +create_repo () { + number_of_commits=$1 + nr=0 + parent= + test -d .git || { + git init && + tree=$(git write-tree) && + while [ $nr -lt $number_of_commits ]; do + test_tick && + commit=$(echo $nr | git commit-tree $tree $parent) || + return + parent="-p $commit" + nr=$(($nr+1)) + done && + git update-ref refs/heads/master $commit && + { + export GIT_INDEX_FILE=.git/temp; + git rev-list HEAD | cat -n | sed "s/^[ ][ ]*/ /g" | + while read nr sha1; do + blob=$(echo note $nr | git hash-object -w --stdin) && + echo $sha1 | sed "s/^/0644 $blob 0 /" + done | git update-index --index-info && + tree=$(git write-tree) && + test_tick && + commit=$(echo notes | git commit-tree $tree) && + git update-ref refs/notes/commits $commit + } && + git config core.notesRef refs/notes/commits + } +} + +test_notes () { + count=$1 && + git config core.notesRef refs/notes/commits && + git log | grep "^ " > output && + i=1 && + while [ $i -le $count ]; do + echo " $(($count-$i))" && + echo " note $i" && + i=$(($i+1)); + done > expect && + git diff expect output +} + +cat > time_notes << \EOF + mode=$1 + i=1 + while [ $i -lt $2 ]; do + case $1 in + no-notes) + export GIT_NOTES_REF=non-existing + ;; + notes) + unset GIT_NOTES_REF + ;; + esac + git log >/dev/null + i=$(($i+1)) + done +EOF + +time_notes () { + for mode in no-notes notes + do + echo $mode + /usr/bin/time sh ../time_notes $mode $1 + done +} + +for count in 10 100 1000 10000; do + + mkdir $count + (cd $count; + + test_expect_success "setup $count" "create_repo $count" + + test_expect_success 'notes work' "test_notes $count" + + test_expect_success 'notes timing' "time_notes 100" + ) +done + +test_done From bb1dff9def343a7d513eea8f4eaa5fd7d5d3fc5f Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 12 Jan 2009 15:16:04 +0100 Subject: [PATCH 5/6] notes: fix core.notesRef documentation The path format was inconsistent with the one used in git-notes.sh: it supposedly split the sha1 in the same 2/38 format that .git/objects uses, but the code uses the full sha1 without a path separator. While at it, also fix a grammatical error. Signed-off-by: Thomas Rast Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index b35a32abe1..2fdca0ea30 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -424,15 +424,13 @@ overlapping IO's. core.notesRef:: When showing commit messages, also show notes which are stored in - the given ref. This ref is expected to contain paths of the form - ??/*, where the directory name consists of the first two - characters of the commit name, and the base name consists of - the remaining 38 characters. + the given ref. This ref is expected to contain files named + after the full SHA-1 of the commit they annotate. + -If such a path exists in the given ref, the referenced blob is read, and +If such a file exists in the given ref, the referenced blob is read, and appended to the commit message, separated by a "Notes:" line. If the given ref itself does not exist, it is not an error, but means that no -notes should be print. +notes should be printed. + This setting defaults to "refs/notes/commits", and can be overridden by the `GIT_NOTES_REF` environment variable. From 22a3d060937072b0f197a8084af879c753c68fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 13 Jan 2009 20:57:16 +0100 Subject: [PATCH 6/6] git-notes: fix printing of multi-line notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The line length was read from the same position every time, causing mangled output when printing notes with multiple lines. Also, adding new-line manually for each line ensures that we get a new-line between commits, matching git-log for commits without notes. Signed-off-by: Tor Arne Vestbø Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- notes.c | 13 +++++++------ t/t3301-notes.sh | 32 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/notes.c b/notes.c index ad43a2ed15..bd737842d9 100644 --- a/notes.c +++ b/notes.c @@ -110,8 +110,8 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, { static const char *utf8 = "utf-8"; unsigned char *sha1; - char *msg; - unsigned long msgoffset, msglen; + char *msg, *msg_p; + unsigned long linelen, msglen; enum object_type type; if (!initialized) { @@ -148,12 +148,13 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, strbuf_addstr(sb, "\nNotes:\n"); - for (msgoffset = 0; msgoffset < msglen;) { - int linelen = strchrnul(msg, '\n') - msg; + for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { + linelen = strchrnul(msg_p, '\n') - msg_p; strbuf_addstr(sb, " "); - strbuf_add(sb, msg + msgoffset, linelen); - msgoffset += linelen; + strbuf_add(sb, msg_p, linelen); + strbuf_addch(sb, '\n'); } + free(msg); } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index ba42c45ec4..9393a25511 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -59,7 +59,37 @@ EOF test_expect_success 'show notes' ' ! (git cat-file commit HEAD | grep b1) && git log -1 > output && - git diff expect output + test_cmp expect output +' +test_expect_success 'create multi-line notes (setup)' ' + : > a3 && + git add a3 && + test_tick && + git commit -m 3rd && + MSG="b3 +c3c3c3c3 +d3d3d3" git notes edit +' + +cat > expect-multiline << EOF +commit 1584215f1d29c65e99c6c6848626553fdd07fd75 +Author: A U Thor +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + b3 + c3c3c3c3 + d3d3d3 +EOF + +printf "\n" >> expect-multiline +cat expect >> expect-multiline + +test_expect_success 'show multi-line notes' ' + git log -2 > output && + test_cmp expect-multiline output ' test_done