With the provided script, edit-commit-annotations, you can add after-the-fact annotations to commits, which will be shown by the log if the config variable core.showannotations is set. The annotations are tracked in a new ref, refs/annotations/commits, in the same fan-out style as .git/objects/??/*, only that they only exist in the object database now. Signed-off-by: Johannes Schindelin <johannes.schindelin@xxxxxx> --- I have the hunch that this will be relatively fast and scalable, since the tree objects are sorted by name (the name being the object name of the to-be-annotated commit). I'm on the run for 15 hours now, but please feel free to discuss and / or trash it while I'm away. cache.h | 1 + commit.c | 48 ++++++++++++++++++++++++++++++++++++++++- config.c | 5 ++++ environment.c | 1 + git-edit-commit-annotation.sh | 46 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletions(-) create mode 100755 git-edit-commit-annotation.sh diff --git a/cache.h b/cache.h index bc6b8e8..4166888 100644 --- a/cache.h +++ b/cache.h @@ -288,6 +288,7 @@ extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern int auto_crlf; +extern int show_commit_annotations; #define GIT_REPO_VERSION 0 extern int repository_format_version; diff --git a/commit.c b/commit.c index 4ca4d44..409ebc8 100644 --- a/commit.c +++ b/commit.c @@ -911,6 +911,48 @@ static long format_commit_message(const struct commit *commit, return strlen(buf); } +static unsigned long show_annotations(const struct commit *commit, + char *buf, unsigned long space) +{ + char name[80]; + const char *hex = sha1_to_hex(commit->object.sha1); + unsigned char sha1[20]; + char *msg; + unsigned long offset = 0, msgoffset = 0, msglen; + enum object_type type; + + snprintf(name, sizeof(name), + "refs/annotations/commits:%.*s/%.*s", + 2, hex, 38, hex + 2); + if (get_sha1(name, sha1)) + return 0; + + if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen) + return 0; + /* we will end the annotation by a newline anyway. */ + if (msg[msglen - 1] == '\n') + msglen--; + + offset += snprintf(buf + offset, space - offset - 1, + "\nAnnotation:\n"); + + for (msgoffset = 0; msgoffset < msglen;) { + int linelen = get_one_line(msg + msgoffset, msglen); + + offset += snprintf(buf + offset, space - offset - 1, + " %.*s", linelen, msg + msgoffset); + + if (offset + 1 >= space) + break; + + msgoffset += linelen; + } + buf[offset++] = '\n'; + free(msg); + + return offset; +} + unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, @@ -1098,8 +1140,12 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, */ if (fmt == CMIT_FMT_EMAIL && !body) buf[offset++] = '\n'; - buf[offset] = '\0'; + if (fmt != CMIT_FMT_ONELINE && show_commit_annotations) + offset += show_annotations(commit, + buf + offset, space - offset); + + buf[offset] = '\0'; free(reencoded); return offset; } diff --git a/config.c b/config.c index 58d3ed5..34db9b2 100644 --- a/config.c +++ b/config.c @@ -356,6 +356,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.showannotaions")) { + show_commit_annotations = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { strlcpy(git_default_name, value, sizeof(git_default_name)); return 0; diff --git a/environment.c b/environment.c index 8b9b89d..c649f19 100644 --- a/environment.c +++ b/environment.c @@ -32,6 +32,7 @@ size_t delta_base_cache_limit = 16 * 1024 * 1024; int pager_in_use; int pager_use_color = 1; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +int show_commit_annotations; static const char *git_dir; static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; diff --git a/git-edit-commit-annotation.sh b/git-edit-commit-annotation.sh new file mode 100755 index 0000000..2abcd34 --- /dev/null +++ b/git-edit-commit-annotation.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +USAGE="[commit-name]" +. git-sh-setup + +test -n "$2" && usage +COMMIT=$(git rev-parse --verify --default HEAD "$@") +NAME=$(echo $COMMIT | sed "s/^../&\//") + +MESSAGE="$GIT_DIR"/COMMIT_ANNOTATION.$$ +git log -1 $COMMIT | sed "s/^/#/" > "$MESSAGE" + +GIT_INDEX_FILE="$MESSAGE".idx +export GIT_INDEX_FILE + +TIPNAME=refs/annotations/commits +OLDTIP=$(git show-ref $TIPNAME | cut -f 1 -d ' ') +if [ -z "$OLDTIP" ]; then + OLDTIP=0000000000000000000000000000000000000000 +else + PARENT="-p $OLDTIP" + git read-tree $TIPNAME || die "Could not read index" + git cat-file blob :$NAME >> "$MESSAGE" 2> /dev/null +fi + +${VISUAL:-${EDITOR:-vi}} "$MESSAGE" + +grep -v ^# < "$MESSAGE" | git stripspace > "$MESSAGE".processed +mv "$MESSAGE".processed "$MESSAGE" +if [ -z "$(cat "$MESSAGE")" ]; then + case $OLDTIP in 0000000000000000000000000000000000000000) + echo "Will not initialise with empty tree" + exit 1 + esac + git update-index --force-remove $NAME || + die "Could not update index" +else + BLOB=$(git hash-object -w "$MESSAGE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $NAME || + die "Could not write index" +fi + +TREE=$(git write-tree) || die "Could not write tree" +NEWTIP=$(date | git commit-tree $TREE $PARENT) || die "Could not annotate" +git update-ref $TIPNAME $NEWTIP $OLDTIP -- 1.5.2.1.2713.gbb6a-dirty - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html