All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] post-rewrite hook and copying notes
@ 2010-02-14 16:17 Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 1/6] Documentation: document post-rewrite hook Thomas Rast
                   ` (7 more replies)
  0 siblings, 8 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

This series implements a post-rewrite hook.  This has been on my todo
list since we first got notes, but I never got around to it.

My original intended use-case was to add a note for the source of the
rewritten commit, but recent discussion on git-notes was that we
should add some feature that lets users copy over their notes when
rewriting commits.  The hook in the last patch, together with the 'git
notes copy' command now in 'pu', can achieve this.

I'm not sure whether we should stop here and declare it the user's
responsibility to enable the hook though.  My idea of a "full
integration" approach would be to instead teach 'git notes copy' to
read the same type of lists that I'm handing to post-rewrite, and then
wire calls to it into the scripts in the same places that call
post-rewrite.  Opinions?

Thomas Rast (6):
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  filter-branch: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  contrib: add a hook that copies notes over rewrites

 Documentation/githooks.txt            |   36 +++++++
 builtin-commit.c                      |   43 ++++++++
 contrib/hooks/post-rewrite-copy-notes |   37 +++++++
 git-am.sh                             |   10 ++
 git-filter-branch.sh                  |   10 ++
 git-rebase--interactive.sh            |   41 +++++++-
 git-rebase.sh                         |    5 +
 t/t5407-post-rewrite-hook.sh          |  180 +++++++++++++++++++++++++++++++++
 8 files changed, 361 insertions(+), 1 deletions(-)
 create mode 100644 contrib/hooks/post-rewrite-copy-notes
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [RFC PATCH 1/6] Documentation: document post-rewrite hook
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 2/6] commit --amend: invoke " Thomas Rast
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/githooks.txt |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..41895e9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,42 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase', 'git-filter-branch').  Its first argument
+denotes the command it was invoked by: currently one of `amend`,
+`rebase`, or `filter-branch`.  Further command-dependent arguments may
+be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
+
+filter-branch::
+	Commits that were processed by 'git-filter-branch', but not
+	changed, are not included in the list.  If the list is empty
+	after this filtering, the hook is not invoked at all.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH 2/6] commit --amend: invoke post-rewrite hook
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 1/6] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 3/6] filter-branch: " Thomas Rast
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-commit.c             |   43 ++++++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin-commit.c b/builtin-commit.c
index 55676fd..9b67649 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		struct commit *commit;
+		commit = lookup_commit(head_sha1);
+		if (!commit || parse_commit(commit))
+			die("HEAD commit disappeared right under my eyes?");
+		run_rewrite_hook(commit->object.sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd14..5735859 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -445,7 +445,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH 3/6] filter-branch: invoke post-rewrite hook
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 1/6] Documentation: document post-rewrite hook Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 2/6] commit --amend: invoke " Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-15 20:36   ` Johannes Sixt
  2010-02-14 16:17 ` [RFC PATCH 4/6] rebase: " Thomas Rast
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-filter-branch.sh         |   10 ++++++++++
 t/t5407-post-rewrite-hook.sh |   20 ++++++++++++++++++++
 2 files changed, 30 insertions(+), 0 deletions(-)

diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 88fb0f0..531cc8e 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -358,6 +358,10 @@ while read commit parents; do
 	@SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
 		$(git write-tree) $parentstr < ../message > ../map/$commit ||
 			die "could not write rewritten commit"
+	new_commit=$(map $commit)
+	if test $commit != $new_commit; then
+		echo $commit $new_commit >> "$workdir"/../rewritten
+	fi
 done <../revs
 
 # If we are filtering for paths, as in the case of a subdirectory
@@ -484,6 +488,12 @@ if [ "$filter_tag_name" ]; then
 fi
 
 cd ../..
+
+if test -x "$GIT_DIR"/hooks/post-rewrite &&
+	test -s "$workdir"/../rewritten; then
+	"$GIT_DIR"/hooks/post-rewrite filter-branch < "$workdir"/../rewritten
+fi
+
 rm -rf "$tempdir"
 
 trap - 0
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..ff23ebf 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,24 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git filter-branch' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f --tree-filter "touch newfile" B..HEAD &&
+	echo filter-branch >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git filter-branch (no-op)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f B..HEAD &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
 test_done
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH 4/6] rebase: invoke post-rewrite hook
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
                   ` (2 preceding siblings ...)
  2010-02-14 16:17 ` [RFC PATCH 3/6] filter-branch: " Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 5/6] rebase -i: " Thomas Rast
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 3c08d53..56428b4 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -575,6 +575,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -582,6 +583,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -768,6 +770,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -776,6 +782,10 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 git gc --auto
 
 rm -fr "$dotest"
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index ff23ebf..d6438a7 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -69,4 +69,34 @@ test_expect_success 'git filter-branch (no-op)' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH 5/6] rebase -i: invoke post-rewrite hook
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
                   ` (3 preceding siblings ...)
  2010-02-14 16:17 ` [RFC PATCH 4/6] rebase: " Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-14 16:17 ` [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites Thomas Rast
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase--interactive.sh   |   39 +++++++++++++++++++++
 t/t5407-post-rewrite-hook.sh |   78 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 0 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5735859..fc4f7e9 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/conflicted-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -446,6 +476,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -454,6 +485,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		make_patch $sha1
+		record_in_rewritten "$sha1"
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
 		warn "You can amend the commit now, with"
@@ -509,6 +541,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -685,6 +723,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/conflicted-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index d6438a7..d3ac469 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -99,4 +99,82 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
                   ` (4 preceding siblings ...)
  2010-02-14 16:17 ` [RFC PATCH 5/6] rebase -i: " Thomas Rast
@ 2010-02-14 16:17 ` Thomas Rast
  2010-02-14 16:21   ` Thomas Rast
  2010-02-14 21:46 ` [PATCH] WIP: git notes copy --stdin Thomas Rast
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
  7 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:17 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

This hook can be used to copy your notes across rewrites.  You must
also choose across which type of rewrite you want to copy them, or
just enable all of them by saying

  git config hooks.rewriteCopyNotes all

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 contrib/hooks/post-rewrite-copy-notes |   37 +++++++++++++++++++++++++++++++++
 1 files changed, 37 insertions(+), 0 deletions(-)
 create mode 100644 contrib/hooks/post-rewrite-copy-notes

diff --git a/contrib/hooks/post-rewrite-copy-notes b/contrib/hooks/post-rewrite-copy-notes
new file mode 100644
index 0000000..73d6a82
--- /dev/null
+++ b/contrib/hooks/post-rewrite-copy-notes
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+# This hook copies over the git-notes annotation from the
+# pre-rewritten commit to the post-rewritten one.  It only does so if
+# the invoking command is listed in hooks.rewriteCopyNotes (which must
+# be space separated).  If hooks.rewriteCopyNotesVerbose is set, it
+# lists the commits for which 'git notes copy' was successful on
+# stderr.
+
+type="$1"
+run=
+
+enabled="$(git config --get hooks.rewriteCopyNotes)"
+if test "$enabled" = all || test "$enabled" = true; then
+    enabled="amend rebase filter-branch"
+fi
+
+for enabled_type in $enabled; do
+    test "$enabled_type" = "$type" && run=t
+done
+
+if test -z "$run"; then
+    exit
+done
+
+verbose=$(git config --get hooks.rewriteCopyNotesVerbose)
+test "$verbose" = true && echo "Copying notes:" >2
+end_msg="No commit had any notes."
+
+while read pre post extra; do
+    git notes copy "$pre" "$post" 2>/dev/null &&
+    end_msg="Done." &&
+    test "$verbose" = true &&
+    echo "    $pre -> $post" >2
+done
+
+test "$verbose" = true && echo "$end_msg" >2
-- 
1.7.0.216.g74d8e

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites
  2010-02-14 16:17 ` [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites Thomas Rast
@ 2010-02-14 16:21   ` Thomas Rast
  0 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 16:21 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

On Sunday 14 February 2010 17:17:11 Thomas Rast wrote:
> +if test -z "$run"; then
> +    exit
> +done

Argh, that's what I get for writing lots of tests except for the PoC
hook.  This should of course say 'fi' instead of 'done'.  Sorry.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH] WIP: git notes copy --stdin
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
                   ` (5 preceding siblings ...)
  2010-02-14 16:17 ` [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites Thomas Rast
@ 2010-02-14 21:46 ` Thomas Rast
  2010-02-15  1:25   ` Johan Herland
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
  7 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-14 21:46 UTC (permalink / raw)
  To: git; +Cc: Johan Herland

This implements a 'git notes copy --stdin' mode suitable to read the
data fed to the post-rewrite hook.
---

Ok, so I changed my mind and decided to implement the "make it
built-in and configurable" way.  This helper should work.  I spent
some time trying to refactor cmd_notes() into something that can
nicely handle commands that do not fit the normal scheme, but
eventually was too tired to continue and just crafted it in the
existing code in the right place.

If nobody beats me to it I'll pick this up later this week, but
tomorrow will be too busy to even think about it.


 builtin-notes.c |   56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 56 insertions(+), 0 deletions(-)

diff --git a/builtin-notes.c b/builtin-notes.c
index 123ecad..3cd9e45 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,6 +278,52 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		const unsigned char *note;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		note = get_note(t, from_obj);
+		if (!force) {
+			const unsigned char *existing_note = get_note(t, to_obj);
+			if (existing_note) {
+				error("Cannot copy notes. Found existing notes for object"
+				      " %s. Use '-f' to overwrite existing notes",
+				      sha1_to_hex(to_obj));
+				return 1;
+			}
+		}
+
+		if (note)
+			add_note(t, to_obj, note, combine_notes_overwrite);
+		else
+			remove_note(t, to_obj);
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	return 0;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -301,6 +347,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &force, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -351,6 +398,15 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
+		if (stdin) {
+			if (argc > 2) {
+				error("too few parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				retval = notes_copy_from_stdin(force);
+				goto end;
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
-- 
1.7.0.224.g30f23.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH] WIP: git notes copy --stdin
  2010-02-14 21:46 ` [PATCH] WIP: git notes copy --stdin Thomas Rast
@ 2010-02-15  1:25   ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-02-15  1:25 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git

On Sunday 14 February 2010, Thomas Rast wrote:
> This implements a 'git notes copy --stdin' mode suitable to read the
> data fed to the post-rewrite hook.
> ---
> 
> Ok, so I changed my mind and decided to implement the "make it
> built-in and configurable" way.  This helper should work.  I spent
> some time trying to refactor cmd_notes() into something that can
> nicely handle commands that do not fit the normal scheme, but
> eventually was too tired to continue and just crafted it in the
> existing code in the right place.
> 
> If nobody beats me to it I'll pick this up later this week, but
> tomorrow will be too busy to even think about it.
> 
> 
>  builtin-notes.c |   56
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed,
> 56 insertions(+), 0 deletions(-)
> 
> diff --git a/builtin-notes.c b/builtin-notes.c
> index 123ecad..3cd9e45 100644
> --- a/builtin-notes.c
> +++ b/builtin-notes.c
> @@ -278,6 +278,52 @@ int commit_notes(struct notes_tree *t, const char
> *msg) return 0;
>  }
> 
> +int notes_copy_from_stdin(int force)
> +{
> +	struct strbuf buf = STRBUF_INIT;
> +	struct notes_tree *t;
> +
> +	init_notes(NULL, NULL, NULL, 0);
> +	t = &default_notes_tree;

As you say, cmd_notes() should be refactored, so that you don't have to 
duplicate all of this (and some of the below stuff).

> +	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
> +		unsigned char from_obj[20], to_obj[20];
> +		struct strbuf **split;
> +		const unsigned char *note;
> +
> +		split = strbuf_split(&buf, ' ');
> +		if (!split[0] || !split[1])
> +			die("Malformed input line: '%s'.", buf.buf);
> +		strbuf_rtrim(split[0]);
> +		strbuf_rtrim(split[1]);
> +		if (get_sha1(split[0]->buf, from_obj))
> +			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
> +		if (get_sha1(split[1]->buf, to_obj))
> +			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
> +
> +		note = get_note(t, from_obj);
> +		if (!force) {
> +			const unsigned char *existing_note = get_note(t, to_obj);
> +			if (existing_note) {
> +				error("Cannot copy notes. Found existing notes for object"
> +				      " %s. Use '-f' to overwrite existing notes",
> +				      sha1_to_hex(to_obj));
> +				return 1;
> +			}
> +		}
> +
> +		if (note)
> +			add_note(t, to_obj, note, combine_notes_overwrite);
> +		else
> +			remove_note(t, to_obj);
> +
> +		strbuf_list_free(split);
> +	}
> +
> +	commit_notes(t, "Notes added by 'git notes copy'");

We (this goes for the existing commit_notes() call in cmd_notes() as well) 
should either check the return value for commit_notes(), or (since it 
currently only returns 0) we should change commit_notes() to void instead of 
int. Actually, the correct solution is probably to "return error()" instead 
of "die()" inside commit_notes(), and then check the return value.

> +	return 0;
> +}
> +
>  int cmd_notes(int argc, const char **argv, const char *prefix)
>  {
>  	struct notes_tree *t;
> @@ -301,6 +347,7 @@ int cmd_notes(int argc, const char **argv, const char
> *prefix) OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
>  			   "reuse specified note object", parse_reuse_arg),
>  		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
> +		OPT_BOOLEAN(0, "stdin", &force, "read objects from stdin"),

Why are you setting &force from "--stdin"?

> @@ -351,6 +398,15 @@ int cmd_notes(int argc, const char **argv, const
> char *prefix)
> 
>  	if (copy) {
>  		const char *from_ref;
> +		if (stdin) {

I don't think this is intended. Isn't stdin (the standard input FILE *) 
_always_ true?

> +			if (argc > 2) {
> +				error("too few parameters");
> +				usage_with_options(git_notes_usage, options);
> +			} else {
> +				retval = notes_copy_from_stdin(force);
> +				goto end;
> +			}
> +		}
>  		if (argc < 3) {
>  			error("too few parameters");
>  			usage_with_options(git_notes_usage, options);

The patch needs to add code that barfs if "--stdin" is used with any 
subcommand other than "copy".

Of course, the patch also needs documentation and tests, but I guess you 
knew that... ;)


I like the intent of this patch, although it needs more work before it's 
finished.


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH 3/6] filter-branch: invoke post-rewrite hook
  2010-02-14 16:17 ` [RFC PATCH 3/6] filter-branch: " Thomas Rast
@ 2010-02-15 20:36   ` Johannes Sixt
  0 siblings, 0 replies; 135+ messages in thread
From: Johannes Sixt @ 2010-02-15 20:36 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johan Herland

Thomas Rast schrieb:
> +if test -x "$GIT_DIR"/hooks/post-rewrite &&
> +	test -s "$workdir"/../rewritten; then
> +	"$GIT_DIR"/hooks/post-rewrite filter-branch < "$workdir"/../rewritten
> +fi

This sounds extra-strange. As if filter-branch is used 20 times a day. If 
the intent is to carry notes over to new commits, then filter-branch 
should grow a --notes-filter instruction, no? With an easily accessible 
copy-everything mode like --tagname-filter=cat.

-- Hannes

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 00/11] post-rewrite / automatic notes copying
  2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
                   ` (6 preceding siblings ...)
  2010-02-14 21:46 ` [PATCH] WIP: git notes copy --stdin Thomas Rast
@ 2010-02-16 23:25 ` Thomas Rast
  2010-02-16 23:25   ` [RFC PATCH v2 01/11] Documentation: document post-rewrite hook Thomas Rast
                     ` (11 more replies)
  7 siblings, 12 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:25 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

I originally had two series, but to retain my sanity while shuffling
fixups, I based the entire batch on a rebase of jh/notes onto current
master.  The first four do not depend on jh/notes though, and are
almost unchanged from the first round.

The docs are probably sprinkled a bit strangely over the patches, not
sure if it's worth a cleanup though (or I could just gather all the
docs in a final patch).  At least tests should be in the right place.


Based on Hannes's comment:

On Monday 15 February 2010 21:36:05 Johannes Sixt wrote:
> Thomas Rast schrieb:
> > +if test -x "$GIT_DIR"/hooks/post-rewrite &&
> > +	test -s "$workdir"/../rewritten; then
> > +	"$GIT_DIR"/hooks/post-rewrite filter-branch < "$workdir"/../rewritten
> > +fi
> 
> This sounds extra-strange. As if filter-branch is used 20 times a day. If 
> the intent is to carry notes over to new commits, then filter-branch 
> should grow a --notes-filter instruction, no? With an easily accessible 
> copy-everything mode like --tagname-filter=cat.

I made filter-branch extra-tweakable, but tried to stay consistent
with the configuration of the rest of the series.  Maybe it's a bit
overengineered now, dunno.

On Monday 15 February 2010 02:25:52 Johan Herland wrote:
> On Sunday 14 February 2010, Thomas Rast wrote:
> > 
> > I spent some time trying to refactor cmd_notes() into something
> > that can nicely handle commands that do not fit the normal scheme,
> > but eventually was too tired to continue and just crafted it in
> > the existing code in the right place.
[...]
> As you say, cmd_notes() should be refactored, so that you don't have to 
> duplicate all of this (and some of the below stuff).

Actually I meant something different: it took me quite some time to
figure out what goes where in the flow of the current main() function,
since it is sprinkled with if(subcommand) -- for most of them, several
such clauses.

I guess it made sense at the time, because all commands more or less
worked with the same arguments and mostly the same background logic,
just in a slightly different way.  But I think it's quite unnatural
for an entirely-different command like 'copy --stdin'.

But maybe that's just me.

> We (this goes for the existing commit_notes() call in cmd_notes() as well) 
> should either check the return value for commit_notes(), or (since it 
> currently only returns 0) we should change commit_notes() to void instead of 
> int. Actually, the correct solution is probably to "return error()" instead 
> of "die()" inside commit_notes(), and then check the return value.

I'll leave that up to your judgement; I haven't looked into the API
workings much, so I'm not sure when it should fail how.

[Thanks for the other comments on my embarrassing mistakes; I was
apparently rather too tired to continue programming.]


Thomas Rast (11):
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: clean up t3301
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  filter-branch: invoke post-rewrite hook
  filter-branch: learn how to filter notes

 Documentation/config.txt            |   17 +++
 Documentation/git-filter-branch.txt |   20 +++-
 Documentation/git-notes.txt         |   16 ++-
 Documentation/githooks.txt          |   40 +++++
 builtin-commit.c                    |   49 +++++++
 builtin-notes.c                     |  134 +++++++++++++++++-
 builtin.h                           |   15 ++
 git-am.sh                           |   13 ++
 git-filter-branch.sh                |   37 +++++
 git-rebase--interactive.sh          |   45 ++++++-
 git-rebase.sh                       |    6 +
 notes.c                             |   21 +++
 notes.h                             |    9 ++
 t/t3301-notes.sh                    |  273 +++++++++++++++++++++++------------
 t/t3400-rebase.sh                   |   16 ++
 t/t3404-rebase-interactive.sh       |   10 ++
 t/t5407-post-rewrite-hook.sh        |  188 ++++++++++++++++++++++++
 t/t7003-filter-branch.sh            |   22 +++
 t/t7501-commit.sh                   |   11 ++
 19 files changed, 843 insertions(+), 99 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 01/11] Documentation: document post-rewrite hook
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
@ 2010-02-16 23:25   ` Thomas Rast
  2010-02-16 23:59     ` Junio C Hamano
  2010-02-16 23:25   ` [RFC PATCH v2 02/11] commit --amend: invoke " Thomas Rast
                     ` (10 subsequent siblings)
  11 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:25 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/githooks.txt |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..41895e9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,42 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase', 'git-filter-branch').  Its first argument
+denotes the command it was invoked by: currently one of `amend`,
+`rebase`, or `filter-branch`.  Further command-dependent arguments may
+be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
+
+filter-branch::
+	Commits that were processed by 'git-filter-branch', but not
+	changed, are not included in the list.  If the list is empty
+	after this filtering, the hook is not invoked at all.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 02/11] commit --amend: invoke post-rewrite hook
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
  2010-02-16 23:25   ` [RFC PATCH v2 01/11] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-16 23:25   ` Thomas Rast
  2010-02-16 23:25   ` [RFC PATCH v2 03/11] rebase: " Thomas Rast
                     ` (9 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:25 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-commit.c             |   43 ++++++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin-commit.c b/builtin-commit.c
index 55676fd..9b67649 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		struct commit *commit;
+		commit = lookup_commit(head_sha1);
+		if (!commit || parse_commit(commit))
+			die("HEAD commit disappeared right under my eyes?");
+		run_rewrite_hook(commit->object.sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd14..5735859 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -445,7 +445,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 03/11] rebase: invoke post-rewrite hook
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
  2010-02-16 23:25   ` [RFC PATCH v2 01/11] Documentation: document post-rewrite hook Thomas Rast
  2010-02-16 23:25   ` [RFC PATCH v2 02/11] commit --amend: invoke " Thomas Rast
@ 2010-02-16 23:25   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 04/11] rebase -i: " Thomas Rast
                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:25 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 3c08d53..56428b4 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -575,6 +575,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -582,6 +583,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -768,6 +770,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -776,6 +782,10 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 git gc --auto
 
 rm -fr "$dotest"
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 04/11] rebase -i: invoke post-rewrite hook
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (2 preceding siblings ...)
  2010-02-16 23:25   ` [RFC PATCH v2 03/11] rebase: " Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 05/11] notes: clean up t3301 Thomas Rast
                     ` (7 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase--interactive.sh   |   39 +++++++++++++++++++++
 t/t5407-post-rewrite-hook.sh |   78 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 0 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5735859..fc4f7e9 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/conflicted-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -446,6 +476,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -454,6 +485,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		make_patch $sha1
+		record_in_rewritten "$sha1"
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
 		warn "You can amend the commit now, with"
@@ -509,6 +541,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -685,6 +723,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/conflicted-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..488d4a0 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,82 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 05/11] notes: clean up t3301
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (3 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 04/11] rebase -i: " Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:52     ` Junio C Hamano
  2010-02-16 23:26   ` [RFC PATCH v2 06/11] notes: implement 'git notes copy --stdin' Thomas Rast
                     ` (6 subsequent siblings)
  11 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

The existing tests handrolled test_commit, so use that instead.

Unfortunately this changes all sha1's.  Since we're not supposed to
unneccessarily rely on them anyway, introduce a small helper that
filters out the commit headers before comparing, and remove them from
the expected outputs.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 t/t3301-notes.sh |  141 ++++++++++++++++++------------------------------------
 1 files changed, 46 insertions(+), 95 deletions(-)

diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 90178f9..3fec7ae 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -7,6 +7,15 @@ test_description='Test commit notes'
 
 . ./test-lib.sh
 
+strip_header () {
+	sed -i '/^\(commit\|tree\|parent\) /d' "$@"
+}
+
+strip_then_cmp () {
+	strip_header "$2"
+	test_cmp "$@"
+}
+
 cat > fake_editor.sh << \EOF
 echo "$MSG" > "$1"
 echo "$MSG" >& 2
@@ -20,14 +29,8 @@ test_expect_success 'cannot annotate non-existing HEAD' '
 '
 
 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_commit 1st &&
+	test_commit 2nd
 '
 
 test_expect_success 'need valid notes ref' '
@@ -92,7 +95,6 @@ test_expect_success 'can overwrite existing note with "git notes add -f"' '
 '
 
 cat > expect << EOF
-commit 268048bfb8a1fb38e703baceb8ab235421bf80c5
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:14:13 2005 -0700
 
@@ -105,21 +107,17 @@ EOF
 test_expect_success 'show notes' '
 	! (git cat-file commit HEAD | grep b1) &&
 	git log -1 > output &&
-	test_cmp expect output
+	strip_then_cmp expect output
 '
 
 test_expect_success 'create multi-line notes (setup)' '
-	: > a3 &&
-	git add a3 &&
-	test_tick &&
-	git commit -m 3rd &&
+	test_commit 3rd &&
 	MSG="b3
 c3c3c3c3
 d3d3d3" git notes add
 '
 
 cat > expect-multiline << EOF
-commit 1584215f1d29c65e99c6c6848626553fdd07fd75
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:15:13 2005 -0700
 
@@ -136,19 +134,15 @@ cat expect >> expect-multiline
 
 test_expect_success 'show multi-line notes' '
 	git log -2 > output &&
-	test_cmp expect-multiline output
+	strip_then_cmp expect-multiline output
 '
 test_expect_success 'create -F notes (setup)' '
-	: > a4 &&
-	git add a4 &&
-	test_tick &&
-	git commit -m 4th &&
+	test_commit 4th &&
 	echo "xyzzy" > note5 &&
 	git notes add -F note5
 '
 
 cat > expect-F << EOF
-commit 15023535574ded8b1a89052b32673f84cf9582b8
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:16:13 2005 -0700
 
@@ -163,13 +157,10 @@ cat expect-multiline >> expect-F
 
 test_expect_success 'show -F notes' '
 	git log -3 > output &&
-	test_cmp expect-F output
+	strip_then_cmp expect-F output
 '
 
 cat >expect << EOF
-commit 15023535574ded8b1a89052b32673f84cf9582b8
-tree e070e3af51011e47b183c33adf9736736a525709
-parent 1584215f1d29c65e99c6c6848626553fdd07fd75
 author A U Thor <author@example.com> 1112912173 -0700
 committer C O Mitter <committer@example.com> 1112912173 -0700
 
@@ -177,7 +168,7 @@ committer C O Mitter <committer@example.com> 1112912173 -0700
 EOF
 test_expect_success 'git log --pretty=raw does not show notes' '
 	git log -1 --pretty=raw >output &&
-	test_cmp expect output
+	strip_then_cmp expect output
 '
 
 cat >>expect <<EOF
@@ -187,7 +178,7 @@ Notes:
 EOF
 test_expect_success 'git log --show-notes' '
 	git log -1 --pretty=raw --show-notes >output &&
-	test_cmp expect output
+	strip_then_cmp expect output
 '
 
 test_expect_success 'git log --no-notes' '
@@ -220,10 +211,7 @@ do
 done
 
 test_expect_success 'create -m notes (setup)' '
-	: > a5 &&
-	git add a5 &&
-	test_tick &&
-	git commit -m 5th &&
+	test_commit 5th &&
 	git notes add -m spam -m "foo
 bar
 baz"
@@ -231,7 +219,6 @@ baz"
 
 whitespace="    "
 cat > expect-m << EOF
-commit bd1753200303d0a0344be813e504253b3d98e74d
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -250,7 +237,7 @@ cat expect-F >> expect-m
 
 test_expect_success 'show -m notes' '
 	git log -4 > output &&
-	test_cmp expect-m output
+	strip_then_cmp expect-m output
 '
 
 test_expect_success 'remove note with add -f -F /dev/null (setup)' '
@@ -258,7 +245,6 @@ test_expect_success 'remove note with add -f -F /dev/null (setup)' '
 '
 
 cat > expect-rm-F << EOF
-commit bd1753200303d0a0344be813e504253b3d98e74d
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:17:13 2005 -0700
 
@@ -270,7 +256,7 @@ cat expect-F >> expect-rm-F
 
 test_expect_success 'verify note removal with -F /dev/null' '
 	git log -4 > output &&
-	test_cmp expect-rm-F output &&
+	strip_then_cmp expect-rm-F output &&
 	! git notes show
 '
 
@@ -280,7 +266,7 @@ test_expect_success 'do not create empty note with -m "" (setup)' '
 
 test_expect_success 'verify non-creation of note with -m ""' '
 	git log -4 > output &&
-	test_cmp expect-rm-F output &&
+	strip_then_cmp expect-rm-F output &&
 	! git notes show
 '
 
@@ -301,7 +287,7 @@ test_expect_success 'create note with combination of -m and -F' '
 	echo "zyxxy" > note_b &&
 	git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" &&
 	git notes show > output &&
-	test_cmp expect-combine_m_and_F output
+	strip_then_cmp expect-combine_m_and_F output
 '
 
 test_expect_success 'remove note with "git notes remove" (setup)' '
@@ -310,13 +296,11 @@ test_expect_success 'remove note with "git notes remove" (setup)' '
 '
 
 cat > expect-rm-remove << EOF
-commit bd1753200303d0a0344be813e504253b3d98e74d
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:17:13 2005 -0700
 
     5th
 
-commit 15023535574ded8b1a89052b32673f84cf9582b8
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:16:13 2005 -0700
 
@@ -328,13 +312,13 @@ cat expect-multiline >> expect-rm-remove
 
 test_expect_success 'verify note removal with "git notes remove"' '
 	git log -4 > output &&
-	test_cmp expect-rm-remove output &&
+	strip_then_cmp expect-rm-remove output &&
 	! git notes show HEAD^
 '
 
 cat > expect << EOF
-c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
-c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
+c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 7a4ca6ee52a974a66cbaa78e33214535dff1d691
+c18dc024e14f08d18d14eea0d747ff692d66d6a3 d07d62e5208f22eb5695e7eb47667dc8b9860290
 EOF
 
 test_expect_success 'list notes with "git notes list"' '
@@ -401,15 +385,11 @@ test_expect_success 'appending empty string to non-existing note does not create
 '
 
 test_expect_success 'create other note on a different notes ref (setup)' '
-	: > a6 &&
-	git add a6 &&
-	test_tick &&
-	git commit -m 6th &&
+	test_commit 6th &&
 	GIT_NOTES_REF="refs/notes/other" git notes add -m "other note"
 '
 
 cat > expect-other << EOF
-commit 387a89921c73d7ed72cd94d179c1c7048ca47756
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -420,7 +400,6 @@ Notes:
 EOF
 
 cat > expect-not-other << EOF
-commit 387a89921c73d7ed72cd94d179c1c7048ca47756
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:18:13 2005 -0700
 
@@ -429,23 +408,23 @@ EOF
 
 test_expect_success 'Do not show note on other ref by default' '
 	git log -1 > output &&
-	test_cmp expect-not-other output
+	strip_then_cmp expect-not-other output
 '
 
 test_expect_success 'Do show note when ref is given in GIT_NOTES_REF' '
 	GIT_NOTES_REF="refs/notes/other" git log -1 > output &&
-	test_cmp expect-other output
+	strip_then_cmp expect-other output
 '
 
 test_expect_success 'Do show note when ref is given in core.notesRef config' '
 	git config core.notesRef "refs/notes/other" &&
 	git log -1 > output &&
-	test_cmp expect-other output
+	strip_then_cmp expect-other output
 '
 
 test_expect_success 'Do not show note when core.notesRef is overridden' '
 	GIT_NOTES_REF="refs/notes/wrong" git log -1 > output &&
-	test_cmp expect-not-other output
+	strip_then_cmp expect-not-other output
 '
 
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
@@ -466,7 +445,6 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
 '
 
 cat > expect << EOF
-commit 2ede89468182a62d0bde2583c736089bcf7d7e92
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:19:13 2005 -0700
 
@@ -477,27 +455,20 @@ Notes:
 EOF
 
 test_expect_success 'create note from other note with "git notes add -C"' '
-	: > a7 &&
-	git add a7 &&
-	test_tick &&
-	git commit -m 7th &&
+	test_commit 7th &&
 	git notes add -C $(git notes list HEAD^) &&
 	git log -1 > actual &&
-	test_cmp expect actual &&
+	strip_then_cmp expect actual &&
 	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
 '
 
 test_expect_success 'create note from non-existing note with "git notes add -C" fails' '
-	: > a8 &&
-	git add a8 &&
-	test_tick &&
-	git commit -m 8th &&
+	test_commit 8th &&
 	test_must_fail git notes add -C deadbeef &&
 	test_must_fail git notes list HEAD
 '
 
 cat > expect << EOF
-commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:21:13 2005 -0700
 
@@ -508,26 +479,19 @@ Notes:
 EOF
 
 test_expect_success 'create note from other note with "git notes add -c"' '
-	: > a9 &&
-	git add a9 &&
-	test_tick &&
-	git commit -m 9th &&
+	test_commit 9th &&
 	MSG="yet another note" git notes add -c $(git notes list HEAD^^) &&
 	git log -1 > actual &&
-	test_cmp expect actual
+	strip_then_cmp expect actual
 '
 
 test_expect_success 'create note from non-existing note with "git notes add -c" fails' '
-	: > a10 &&
-	git add a10 &&
-	test_tick &&
-	git commit -m 10th &&
+	test_commit 10th &&
 	test_must_fail MSG="yet another note" git notes add -c deadbeef &&
 	test_must_fail git notes list HEAD
 '
 
 cat > expect << EOF
-commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:21:13 2005 -0700
 
@@ -542,11 +506,10 @@ EOF
 test_expect_success 'append to note from other note with "git notes append -C"' '
 	git notes append -C $(git notes list HEAD^) HEAD^ &&
 	git log -1 HEAD^ > actual &&
-	test_cmp expect actual
+	strip_then_cmp expect actual
 '
 
 cat > expect << EOF
-commit ffed603236bfa3891c49644257a83598afe8ae5a
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:22:13 2005 -0700
 
@@ -559,11 +522,10 @@ EOF
 test_expect_success 'create note from other note with "git notes append -c"' '
 	MSG="other note" git notes append -c $(git notes list HEAD^) &&
 	git log -1 > actual &&
-	test_cmp expect actual
+	strip_then_cmp expect actual
 '
 
 cat > expect << EOF
-commit ffed603236bfa3891c49644257a83598afe8ae5a
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:22:13 2005 -0700
 
@@ -578,11 +540,10 @@ EOF
 test_expect_success 'append to note from other note with "git notes append -c"' '
 	MSG="yet another note" git notes append -c $(git notes list HEAD) &&
 	git log -1 > actual &&
-	test_cmp expect actual
+	strip_then_cmp expect actual
 '
 
 cat > expect << EOF
-commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:23:13 2005 -0700
 
@@ -595,25 +556,21 @@ $whitespace
 EOF
 
 test_expect_success 'copy note with "git notes copy"' '
-	: > a11 &&
-	git add a11 &&
-	test_tick &&
-	git commit -m 11th &&
+	test_commit 11th &&
 	git notes copy HEAD^ HEAD &&
 	git log -1 > actual &&
-	test_cmp expect actual &&
+	strip_then_cmp expect actual &&
 	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
 '
 
 test_expect_success 'prevent overwrite with "git notes copy"' '
 	test_must_fail git notes copy HEAD~2 HEAD &&
 	git log -1 > actual &&
-	test_cmp expect actual &&
+	strip_then_cmp expect actual &&
 	test "$(git notes list HEAD)" = "$(git notes list HEAD^)"
 '
 
 cat > expect << EOF
-commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:23:13 2005 -0700
 
@@ -628,19 +585,13 @@ EOF
 test_expect_success 'allow overwrite with "git notes copy -f"' '
 	git notes copy -f HEAD~2 HEAD &&
 	git log -1 > actual &&
-	test_cmp expect actual &&
+	strip_then_cmp expect actual &&
 	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)"
 '
 
 test_expect_success 'cannot copy note from object without notes' '
-	: > a12 &&
-	git add a12 &&
-	test_tick &&
-	git commit -m 12th &&
-	: > a13 &&
-	git add a13 &&
-	test_tick &&
-	git commit -m 13th &&
+	test_commit 12th &&
+	test_commit 13th &&
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 06/11] notes: implement 'git notes copy --stdin'
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (4 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 05/11] notes: clean up t3301 Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-notes.txt |   12 ++++++++-
 builtin-notes.c             |   57 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   21 ++++++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   32 ++++++++++++++++++++++++
 5 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 14f73b9..f67cb6a 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -55,6 +55,16 @@ copy::
 	objects has none. (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin-notes.c b/builtin-notes.c
index 123ecad..30420d1 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,6 +278,46 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	struct notes_rewrite_cfg *c = NULL;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -287,7 +327,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -301,6 +341,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -349,8 +390,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				retval = notes_copy_from_stdin(force);
+				goto end;
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index 3ba3e6d..a5ff723 100644
--- a/notes.c
+++ b/notes.c
@@ -1030,3 +1030,24 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note;
+
+	note = get_note(t, from_obj);
+	if (!force) {
+		const unsigned char *existing_note = get_note(t, to_obj);
+		if (existing_note)
+			return 1;
+	}
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else
+		remove_note(t, to_obj);
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index bad03cc..b7547bf 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 3fec7ae..9396080 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -595,4 +595,36 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes:
+    yet another note
+$whitespace
+    yet another note
+
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes:
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	strip_then_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (5 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 06/11] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:58     ` Junio C Hamano
  2010-02-16 23:26   ` [RFC PATCH v2 08/11] rebase: support automatic notes copying Thomas Rast
                     ` (4 subsequent siblings)
  11 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/config.txt    |   13 ++++++
 Documentation/git-notes.txt |    4 ++
 Documentation/githooks.txt  |    4 ++
 builtin-notes.c             |   89 +++++++++++++++++++++++++++++++++++---
 builtin.h                   |   15 ++++++
 t/t3301-notes.sh            |  100 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 219 insertions(+), 6 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 4c36aa9..403f5cf 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1300,6 +1300,19 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `false`.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index f67cb6a..92f1249 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -34,6 +34,10 @@ 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".
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 41895e9..c33a38e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin-notes.c b/builtin-notes.c
index 30420d1..116dbf6 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,15 +278,81 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!strcmp(k, "notes.rewritemode")) {
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	c->cmd = cmd;
+	c->enabled = 0;
+	c->combine = combine_notes_concatenate;
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled) {
+		free(c);
+		return NULL;
+	}
+	init_notes(NULL, NULL, NULL, 0);
+	c->tree = &default_notes_tree;
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	return copy_note(c->tree, from_obj, to_obj, 1, c->combine);
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	commit_notes(c->tree, "Notes added by 'git notes copy'");
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
 	struct notes_tree *t;
 	struct notes_rewrite_cfg *c = NULL;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -303,7 +369,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -330,6 +400,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -342,6 +413,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -390,6 +463,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -397,12 +474,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				retval = notes_copy_from_stdin(force);
+				retval = notes_copy_from_stdin(force, rewrite_cmd);
 				goto end;
 			}
 		}
diff --git a/builtin.h b/builtin.h
index cdf9847..f71f44f 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,21 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree *tree;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 9396080..c91a714 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -627,4 +627,104 @@ test_expect_success 'git notes copy --stdin' '
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	strip_then_cmp expect output
+'
+
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes:
+    yet another note
+$whitespace
+    yet another note
+
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes:
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewrite.foo true &&
+	git config notes.rewriteMode overwrite &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	strip_then_cmp expect output
+'
+
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes:
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	strip_then_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	strip_then_cmp expect output
+'
+
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes:
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	strip_then_cmp expect output
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 08/11] rebase: support automatic notes copying
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (6 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 09/11] commit --amend: copy notes to the new commit Thomas Rast
                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   16 ++++++++++++++++
 t/t3404-rebase-interactive.sh |   10 ++++++++++
 5 files changed, 35 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 56428b4..83cf6e7 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -782,8 +782,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 git gc --auto
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index fc4f7e9..1fda620 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -570,6 +570,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..4608196 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,20 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..5b5104c 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,14 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 09/11] commit --amend: copy notes to the new commit
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (7 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 08/11] rebase: support automatic notes copying Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 10/11] filter-branch: invoke post-rewrite hook Thomas Rast
                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-commit.c  |    6 ++++++
 t/t7501-commit.sh |   11 +++++++++++
 2 files changed, 17 insertions(+), 0 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 9b67649..1943615 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -1341,9 +1341,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
 		struct commit *commit;
+		struct notes_rewrite_cfg *cfg;
 		commit = lookup_commit(head_sha1);
 		if (!commit || parse_commit(commit))
 			die("HEAD commit disappeared right under my eyes?");
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, commit->object.sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(commit->object.sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..93d3c81 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,15 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 10/11] filter-branch: invoke post-rewrite hook
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (8 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 09/11] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-16 23:26   ` [RFC PATCH v2 11/11] filter-branch: learn how to filter notes Thomas Rast
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

With a twist: since this is supposed to be the be-all end-all of
rewriting, we give the user the chance to override the hook.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-filter-branch.txt |    7 ++++++-
 git-filter-branch.sh                |   21 +++++++++++++++++++++
 t/t5407-post-rewrite-hook.sh        |   28 ++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 020028c..28a705f 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -12,7 +12,7 @@ SYNOPSIS
 	[--index-filter <command>] [--parent-filter <command>]
 	[--msg-filter <command>] [--commit-filter <command>]
 	[--tag-name-filter <command>] [--subdirectory-filter <directory>]
-	[--prune-empty]
+	[--prune-empty] [--post-rewrite <command>]
 	[--original <namespace>] [-d <directory>] [-f | --force]
 	[--] [<rev-list options>...]
 
@@ -182,6 +182,11 @@ the nearest ancestor that was not excluded.
 	of the `git commit-tree "$@"` idiom in your commit filter to make that
 	happen.
 
+--post-rewrite <command>::
+	Overrides the post-rewrite hook (if you have one).  To bypass
+	the hook for this invocation, pass `:`.  See
+	linkgit:githooks[1].
+
 --original <namespace>::
 	Use this option to set the namespace where the original commits
 	will be stored. The default value is 'refs/original'.
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 88fb0f0..301c497 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -101,6 +101,7 @@ USAGE="[--env-filter <command>] [--tree-filter <command>]
             [--index-filter <command>] [--parent-filter <command>]
             [--msg-filter <command>] [--commit-filter <command>]
             [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+	    [--post-rewrite <command>]
             [--original <namespace>] [-d <directory>] [-f | --force]
             [<rev-list options>...]"
 
@@ -126,6 +127,13 @@ orig_namespace=refs/original/
 force=
 prune_empty=
 remap_to_ancestor=
+
+if test -x "$GIT_DIR"/hooks/post-rewrite; then
+	post_rewrite=$(git rev-parse --sq-quote "$GIT_DIR"/hooks/post-rewrite)
+else
+	post_rewrite=:
+fi
+
 while :
 do
 	case "$1" in
@@ -190,6 +198,10 @@ do
 		filter_subdir="$OPTARG"
 		remap_to_ancestor=t
 		;;
+	--post-rewrite)
+		post_rewrite="$OPTARG"
+		post_rewrite_given=t
+		;;
 	--original)
 		orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
 		;;
@@ -358,6 +370,10 @@ while read commit parents; do
 	@SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
 		$(git write-tree) $parentstr < ../message > ../map/$commit ||
 			die "could not write rewritten commit"
+	new_commit=$(map $commit)
+	if test $commit != $new_commit; then
+		echo $commit $new_commit >> "$workdir"/../rewritten
+	fi
 done <../revs
 
 # If we are filtering for paths, as in the case of a subdirectory
@@ -484,6 +500,11 @@ if [ "$filter_tag_name" ]; then
 fi
 
 cd ../..
+
+if test -s "$workdir"/../rewritten; then
+	eval "$post_rewrite" 'filter-branch < "$workdir"/../rewritten'
+fi
+
 rm -rf "$tempdir"
 
 trap - 0
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 488d4a0..fa81fd4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -157,4 +157,32 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git filter-branch' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f --tree-filter "touch newfile" B..HEAD &&
+	echo filter-branch >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git filter-branch (no-op)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f B..HEAD &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_expect_success 'git filter-branch --post-rewrite :' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f --post-rewrite : --tree-filter "touch newfile" B..HEAD &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
 test_done
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v2 11/11] filter-branch: learn how to filter notes
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (9 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 10/11] filter-branch: invoke post-rewrite hook Thomas Rast
@ 2010-02-16 23:26   ` Thomas Rast
  2010-02-17 19:59     ` Johannes Sixt
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
  11 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-16 23:26 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Teach filter-branch a new option --notes-filter that acts as a pipe
filter (much like --msg-filter) for the commit notes.  For consistency
with the note copying support in amend/rebase, it defaults to 'cat' if
notes.rewrite.filter-branch is set.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/config.txt            |   12 ++++++++----
 Documentation/git-filter-branch.txt |   13 +++++++++++++
 Documentation/githooks.txt          |    8 ++++----
 git-filter-branch.sh                |   18 +++++++++++++++++-
 t/t7003-filter-branch.sh            |   22 ++++++++++++++++++++++
 5 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 403f5cf..2dfab6d 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1301,10 +1301,10 @@ mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
 notes.rewrite.<command>::
-	When rewriting commits with <command> (currently `amend` or
-	`rebase`) and this variable is set to `true`, git
-	automatically copies your notes from the original to the
-	rewritten commit.  Defaults to `false`.
+	When rewriting commits with <command> (currently `amend`,
+	`rebase` or `filter-branch`) and this variable is set to
+	`true`, git automatically copies your notes from the original
+	to the rewritten commit.  Defaults to `false`.
 
 notes.rewriteMode::
 	When copying notes during a rewrite (see the
@@ -1312,6 +1312,10 @@ notes.rewriteMode::
 	the target commit already has a note.  Must be one of
 	`overwrite`, `concatenate`, or `ignore`.  Defaults to
 	`concatenate`.
++
+Note that 'git-filter-branch' ignores this setting and always
+overwrites; see the description of `--notes-filter` in
+linkgit:git-filter-branch[1].
 
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 28a705f..6fd97d7 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -161,6 +161,19 @@ to other tags will be rewritten to point to the underlying commit.
 	The result will contain that directory (and only that) as its
 	project root.  Implies --remap-to-ancestor.
 
+--notes-filter <command>::
+	This filter rewrites the notes (see linkgit:git-notes[1]).  It
+	gets the old notes on standard input, and its standard output
+	is written as the new note (overwriting any existing notes).
++
+Defaults to doing nothing, unless the `notes.rewrite.filter-branch`
+option is set, in which case it copies the notes (i.e., defaults to
+`cat`).
++
+In addition to the usual variables, GIT_NEW_COMMIT is set to the
+result of the rewriting so that the filter can get at the existing
+notes, if any.
+
 --remap-to-ancestor::
 	Rewrite refs to the nearest rewritten ancestor instead of
 	ignoring them.
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index c33a38e..f9252dd 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -350,11 +350,11 @@ rebase::
 filter-branch::
 	Commits that were processed by 'git-filter-branch', but not
 	changed, are not included in the list.  If the list is empty
-	after this filtering, the hook is not invoked at all.
+	after this filtering, the hook is not invoked at all.  Also
+	see the `--post-rewrite` option in
+	linkgit:git-filter-branch[1].
 
-There is no default 'post-rewrite' hook, but see the
-`post-receive-copy-notes` script in `contrib/hooks` for an example
-that copies your git-notes to the rewritten commits.
+There is no default 'post-rewrite' hook.
 
 
 GIT
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 301c497..ad2da4e 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -101,7 +101,7 @@ USAGE="[--env-filter <command>] [--tree-filter <command>]
             [--index-filter <command>] [--parent-filter <command>]
             [--msg-filter <command>] [--commit-filter <command>]
             [--tag-name-filter <command>] [--subdirectory-filter <directory>]
-	    [--post-rewrite <command>]
+	    [--post-rewrite <command>] [--notes-filter <command>]
             [--original <namespace>] [-d <directory>] [-f | --force]
             [<rev-list options>...]"
 
@@ -134,6 +134,12 @@ else
 	post_rewrite=:
 fi
 
+if test "$(git config --bool notes.rewrite.filter-branch)"; then
+	filter_notes=cat
+else
+	filter_notes=
+fi
+
 while :
 do
 	case "$1" in
@@ -198,6 +204,9 @@ do
 		filter_subdir="$OPTARG"
 		remap_to_ancestor=t
 		;;
+	--notes-filter)
+		filter_notes="$OPTARG"
+		;;
 	--post-rewrite)
 		post_rewrite="$OPTARG"
 		post_rewrite_given=t
@@ -374,6 +383,13 @@ while read commit parents; do
 	if test $commit != $new_commit; then
 		echo $commit $new_commit >> "$workdir"/../rewritten
 	fi
+	if test -n "$filter_notes"; then
+		if git notes show $commit >../note 2>/dev/null; then
+			export GIT_NEW_COMMIT="$new_commit"
+			eval "$filter_notes" <../note |
+			git notes add -F- -f $new_commit
+		fi
+	fi
 done <../revs
 
 # If we are filtering for paths, as in the case of a subdirectory
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 0da13a8..6dc21e8 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -306,6 +306,28 @@ test_expect_success '--remap-to-ancestor with filename filters' '
 	test $orig_invariant = $(git rev-parse invariant)
 '
 
+test_expect_success '--notes-filter off' '
+	git checkout master &&
+	test_commit notes-foo &&
+	git notes add -mtestnote &&
+	git filter-branch -f --tree-filter "touch notes-1" HEAD^.. &&
+	test_must_fail git notes show
+'
+
+test_expect_success '--notes-filter manually' '
+	git reset --hard notes-foo &&
+	git filter-branch -f --notes-filter "sed s/test/foo/" \
+		--tree-filter "touch notes-2" HEAD^.. &&
+	test foonote = "$(git notes show)"
+'
+
+test_expect_success '--notes-filter implicit' '
+	git reset --hard notes-foo &&
+	git config notes.rewrite.filter-branch true &&
+	git filter-branch -f --tree-filter "touch notes-3" HEAD^.. &&
+	test testnote = "$(git notes show)"
+'
+
 test_expect_success 'setup submodule' '
 	rm -fr ?* .git &&
 	git init &&
-- 
1.7.0.53.g5c2e6.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 05/11] notes: clean up t3301
  2010-02-16 23:26   ` [RFC PATCH v2 05/11] notes: clean up t3301 Thomas Rast
@ 2010-02-16 23:52     ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-16 23:52 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> The existing tests handrolled test_commit, so use that instead.
>
> Unfortunately this changes all sha1's.  Since we're not supposed to
> unneccessarily rely on them anyway, introduce a small helper that
> filters out the commit headers before comparing, and remove them from
> the expected outputs.
>
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>
> ---
>  t/t3301-notes.sh |  141 ++++++++++++++++++------------------------------------
>  1 files changed, 46 insertions(+), 95 deletions(-)
>
> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 90178f9..3fec7ae 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -7,6 +7,15 @@ test_description='Test commit notes'
>  
>  . ./test-lib.sh
>  
> +strip_header () {
> +	sed -i '/^\(commit\|tree\|parent\) /d' "$@"
> +}

I do not think -i is portable.  Neither the use of ERE elements by quoting
them with backslash (which is a non-POSIX GNU extension IIRC).

You use this only for one side of test_cmp, so you should be able to do
something like:

	strip_then_cmp () {
        	sed -e "/^[ctp][a-z]* $x40$/d" "$2" |
                test_cmp "$1" -
	}

But because you are using test_commit to freeze the time and get stable
output, and because you still have something like this anyway:

>  cat > expect << EOF
> -c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75
> -c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5
> +c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 7a4ca6ee52a974a66cbaa78e33214535dff1d691
> +c18dc024e14f08d18d14eea0d747ff692d66d6a3 d07d62e5208f22eb5695e7eb47667dc8b9860290
>  EOF

it might be easier to do without such a niceness.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite
  2010-02-16 23:26   ` [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-16 23:58     ` Junio C Hamano
  2010-02-17  0:09       ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-16 23:58 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Implement helper functions to load the rewriting config, and to
> actually copy the notes.  Also document the config.

I find it somewhat unnerving that this series is a bit overengineered to
have fancier and facier handling of notes, while at the same time still
sticking to the "only one default notes tree" mode of operation.  Are you
digging a deep hole that you will find hard to get out of when you finally
need to support copying/rewriting notes from multiple trees at the same
time?

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 01/11] Documentation: document post-rewrite hook
  2010-02-16 23:25   ` [RFC PATCH v2 01/11] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-16 23:59     ` Junio C Hamano
  2010-02-17  0:18       ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-16 23:59 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> +rebase::
> +	For the 'squash' and 'fixup' operation, all commits that were
> +	squashed are listed as being rewritten to the squashed commit.
> +	This means that there will be several lines sharing the same
> +	'new-sha1'.

Similar to filter-branch being silent on dropped commits, I presume
"rebase -i" will stay silent if a pick is removed, or rebase --skip is
given.

I wondered if squash/fixup case wants to distinguish the "primary" commit
and "follow-up" fixup commits (not a complaint, but thinking-aloud), but I
think the rebase command itself and this hook have the same amount of
information needed to guess which one is the primary and which ones are
the follow ups, so it probably is Ok.

Is the order of input lines guaranteed in some way?  For example, if I run
rebase to make A B C into A' and B+C, do we get these three lines:

	A -> A'
        B -> B+C
        C -> B+C

in some known order?  If so, should we document that order?

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite
  2010-02-16 23:58     ` Junio C Hamano
@ 2010-02-17  0:09       ` Thomas Rast
  2010-02-17  0:18         ` Junio C Hamano
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-17  0:09 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

On Wednesday 17 February 2010 00:58:18 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > Implement helper functions to load the rewriting config, and to
> > actually copy the notes.  Also document the config.
> 
> I find it somewhat unnerving that this series is a bit overengineered to
> have fancier and facier handling of notes, while at the same time still
> sticking to the "only one default notes tree" mode of operation.  Are you
> digging a deep hole that you will find hard to get out of when you finally
> need to support copying/rewriting notes from multiple trees at the same
> time?

Hmm, very good point.

I believe the code in this patch is fairly well-prepared to deal with
it.  It would merely need to learn to store a list of trees in the
notes_rewrite_cfg, provided that it's ok to load them all
simultaneously.

However, I haven't really thought about having several notes trees.
Is this automatic copying the only operation that needs this feature,
or were you thinking of others too?  Is it enough to, say, introduce a
way of configuring it at the branch level, or declaring that we
rewrite everything in refs/notes, or some such?

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite
  2010-02-17  0:09       ` Thomas Rast
@ 2010-02-17  0:18         ` Junio C Hamano
  2010-02-20 14:58           ` [WIP/RFC PATCH] Support showing notes from more than one notes tree Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-17  0:18 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> I believe the code in this patch is fairly well-prepared to deal with
> it.  It would merely need to learn to store a list of trees in the
> notes_rewrite_cfg, provided that it's ok to load them all
> simultaneously.

I was mentally handwaving to myself that all will be well in the end ;-)
but thought that it would never hurt to ask if you have thought things
through.

> However, I haven't really thought about having several notes trees.
> Is this automatic copying the only operation that needs this feature,
> or were you thinking of others too?

I was envisioning that most of the time people would want to read from
(and probably concatenate, or output with the origin information tagged in)
multiple notes but want to create into a single one.  But updating for
rewritten commits fundamentally need to happen in all of these multiple
notes trees they would want to read from.

For example, if I have amlog notes to record the message-id of the
original message I ran "am" to queue people's patches, I would want to
carry that even after I make typofixes in the commit log message or in the
code using "commit --amend".  If you have buildlog notes to record the
output from continuously running build-test-bot, you probably do not want
these notes to get carried forward when you rebase a branch.

In "log --show-notes=amlog --show-notes=buildlog" output, we will see
notes from both amlog and buildlog in the original history, but only the
notes from amlog will be shown in the rebased history.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 01/11] Documentation: document post-rewrite hook
  2010-02-16 23:59     ` Junio C Hamano
@ 2010-02-17  0:18       ` Thomas Rast
  2010-02-17  0:29         ` Junio C Hamano
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-17  0:18 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

On Wednesday 17 February 2010 00:59:51 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > +rebase::
> > +	For the 'squash' and 'fixup' operation, all commits that were
> > +	squashed are listed as being rewritten to the squashed commit.
> > +	This means that there will be several lines sharing the same
> > +	'new-sha1'.
> 
> Similar to filter-branch being silent on dropped commits, I presume
> "rebase -i" will stay silent if a pick is removed, or rebase --skip is
> given.

Indeed.  Due to the way that rebase works, it would be a hassle to
report '00..00' (or so) for dropped commits, so I'm not too keen on
going there.  (My intended use-cases don't require it.  Do you have
one that does?)  Documenting this absence wouldn't hurt though.

> I wondered if squash/fixup case wants to distinguish the "primary" commit
> and "follow-up" fixup commits (not a complaint, but thinking-aloud), but I
> think the rebase command itself and this hook have the same amount of
> information needed to guess which one is the primary and which ones are
> the follow ups, so it probably is Ok.

That's what I put in the <extra-info> for :-)

> Is the order of input lines guaranteed in some way?  For example, if I run
> rebase to make A B C into A' and B+C, do we get these three lines:
> 
> 	A -> A'
>         B -> B+C
>         C -> B+C
> 
> in some known order?  If so, should we document that order?

Currently they're in the order that they were rewritten, but what
would the hook learn from that?

I left it unspecified because I wasn't sure we would always have the
list in a specific order (e.g., when reconstructed from a hash map of
rewritings, it's not) but then, keeping the list ordered isn't that
expensive either...

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 01/11] Documentation: document post-rewrite hook
  2010-02-17  0:18       ` Thomas Rast
@ 2010-02-17  0:29         ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-17  0:29 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> On Wednesday 17 February 2010 00:59:51 Junio C Hamano wrote:
>> Thomas Rast <trast@student.ethz.ch> writes:
>> 
>> > +rebase::
>> > +	For the 'squash' and 'fixup' operation, all commits that were
>> > +	squashed are listed as being rewritten to the squashed commit.
>> > +	This means that there will be several lines sharing the same
>> > +	'new-sha1'.
>> 
>> Similar to filter-branch being silent on dropped commits, I presume
>> "rebase -i" will stay silent if a pick is removed, or rebase --skip is
>> given.
>
> Indeed.  Due to the way that rebase works, it would be a hassle to
> report '00..00' (or so) for dropped commits, so I'm not too keen on
> going there.  (My intended use-cases don't require it.  Do you have
> one that does?)  Documenting this absence wouldn't hurt though.

I was solely interested in the documentation at this point.  I don't see a
reason to report "old -> nul"; somebody else may, but I don't.

>> Is the order of input lines guaranteed in some way?  For example, if I run
>> rebase to make A B C into A' and B+C, do we get these three lines:
>> 
>> 	A -> A'
>>         B -> B+C
>>         C -> B+C
>> 
>> in some known order?  If so, should we document that order?
>
> Currently they're in the order that they were rewritten, but what
> would the hook learn from that?

For example, a user may say "I _always_ make a big commit that describes
everything at the beginning, and then follow-ups are irrelevant", in which
case the user may want to say "notes for B carries to B+C but discard
anything attached to C" in _his_ hook.

Another example.  A build-bot may attach "builds fine" note to C but B may
have "failed to build" note without the fix-up in C.  B+C will want to
have the "builds fine" note from C (you would probably also need to verify
that C^{tree} == (B+C)^{tree} before moving the note, but the point is you
do not even want to consider moving it from B to B+C).

These become possible if the output at least guarantees topological
ordering for things that are squashed into one.

But again I am just thinking aloud.  I don't know how much benefit will
such a guarantee give us in practice.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 11/11] filter-branch: learn how to filter notes
  2010-02-16 23:26   ` [RFC PATCH v2 11/11] filter-branch: learn how to filter notes Thomas Rast
@ 2010-02-17 19:59     ` Johannes Sixt
  2010-02-17 23:06       ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Johannes Sixt @ 2010-02-17 19:59 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johan Herland

Thomas Rast schrieb:
> +--notes-filter <command>::
> +	This filter rewrites the notes (see linkgit:git-notes[1]).  It
> +	gets the old notes on standard input, and its standard output
> +	is written as the new note (overwriting any existing notes).
> ++
> +Defaults to doing nothing, unless the `notes.rewrite.filter-branch`
> +option is set, in which case it copies the notes (i.e., defaults to
> +`cat`).
> ++
> +In addition to the usual variables, GIT_NEW_COMMIT is set to the
> +result of the rewriting so that the filter can get at the existing
> +notes, if any.

I think that this form of --notes-filter is not flexible enough.

1. There is no possibility for the filter to decide whether the note 
should be attached to the rewritten commit or not.

2. The implementation is not concerned about different notes namespaces 
(IIUC).

3. There is probably more.

One use-case that comes to mind is to move notes from one particular 
namespace to a different one (and I mean move, not just copy). Don't know 
if 'git notes' itself has such a feature.

I am not a 'git notes' user at this time, nor do I know anything about the 
implementation, nor did I follow any discussions about notes, hence, I'm 
not in the position to judge what --notes-filter could be useful for. I 
know I talked you into implementing it, but that is just because I dislike 
that filter-branch calls a post-rewrite hook (that just doesn't sound 
right to me, given the one-shot nature of filter-branch), and your 
intention obviously was to transfer notes.

-- Hannes

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v2 11/11] filter-branch: learn how to filter notes
  2010-02-17 19:59     ` Johannes Sixt
@ 2010-02-17 23:06       ` Thomas Rast
  0 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-17 23:06 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: git, Johan Herland

[-- Attachment #1: Type: Text/Plain, Size: 2045 bytes --]

On Wednesday 17 February 2010 20:59:26 Johannes Sixt wrote:
> I think that this form of --notes-filter is not flexible enough.
> 
> 1. There is no possibility for the filter to decide whether the note 
> should be attached to the rewritten commit or not.

That's not 100% correct, but it's not your fault for not knowing: if
you add an empty note, that's the same as removing it.

> 2. The implementation is not concerned about different notes namespaces 
> (IIUC).
> 
> 3. There is probably more.
> 
> One use-case that comes to mind is to move notes from one particular 
> namespace to a different one (and I mean move, not just copy). Don't know 
> if 'git notes' itself has such a feature.
> 
> I am not a 'git notes' user at this time, nor do I know anything about the 
> implementation, nor did I follow any discussions about notes, hence, I'm 
> not in the position to judge what --notes-filter could be useful
> for.

I'm currently leaning towards declaring this the realm of
--post-rewrite, not --notes-filter.  I'm not saying that the former is
the perfect / most convenient format in which such a hook could be
specified, but I believe its input should just be pre and post rewrite
sha1.  Using it to move notes, or whatever, should be up to the user.

That's not to say that there shouldn't be some convenient interface to
manage several notes trees, which I'm not yet clear on.

> I know I talked you into implementing it, but that is just because I dislike 
> that filter-branch calls a post-rewrite hook (that just doesn't sound 
> right to me, given the one-shot nature of filter-branch), and your 
> intention obviously was to transfer notes.

No (as I said in the original series leader) my original motivation
was to have a hook that can record the pre-write sha1 in a note on the
rewritten commit.  I noticed that it could also be used to transfer
the notes to the rewritten commit, which was part of the reason why I
implemented it.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [WIP/RFC PATCH] Support showing notes from more than one notes tree
  2010-02-17  0:18         ` Junio C Hamano
@ 2010-02-20 14:58           ` Thomas Rast
  2010-02-20 15:23             ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 14:58 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.
---

I'm running out of git time today, so I'm floating this weatherbaloon
to demonstrate how I would like to get around the limitations
discussed by Junio:

> I was envisioning that most of the time people would want to read from
> (and probably concatenate, or output with the origin information tagged in)
> multiple notes but want to create into a single one.  But updating for
> rewritten commits fundamentally need to happen in all of these multiple
> notes trees they would want to read from.

This patch is the display-several-trees bit.  I'm assuming that it's
ok to load all notes trees at once.  (Obviously it lacks docs, and it
also breaks test because of the new formatting that shows 'Notes from
notes/someref'.  Please ignore those for now.)

My actual intent was to allow more than one notes.displayref config
setting, and loading all of them.  But this doesn't seem to work; a
debugging print confirms that notes_display_config sees the entry, but
another debugging print shows that it only ever loads one of them.
What am I doing wrong?

I plan on having another set of configs called notes.rewriteRef that
gives the list of refs that should have their notes rewritten
automatically along with their commits.  [The rewriteMode setting
could become '<mode> for <ref>' if we want to let the user configure
it on a per-ref/glob basis.]

Finally, git-notes should probably grow a --ref option that serves as
a more convenient way of setting GIT_NOTES_REF.


 notes.c  |   82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 notes.h  |    1 +
 pretty.c |    6 ++--
 3 files changed, 80 insertions(+), 9 deletions(-)

diff --git a/notes.c b/notes.c
index a5ff723..47eaa21 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+struct string_list display_notes_refs;
+struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,36 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int read_one_display_ref(const char *path, const unsigned char *sha1,
+				int flag, void *cb)
+{
+	string_list_insert(path, &display_notes_refs);
+	return 0;
+}
+
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	if (!strcmp(k, "notes.displayref")) {
+		for_each_glob_ref(read_one_display_ref, v, NULL);
+		return 0;
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref()
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +875,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +899,29 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	int *counter = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	display_notes_trees[(*counter)++] = t;
+	return 0;
+}
+
+void init_display_notes()
+{
+	int cb_counter = 0;
+	string_list_insert(default_notes_ref(), &display_notes_refs);
+	display_notes_refs.strdup_strings = 1;
+	git_config(notes_display_config, NULL);
+	display_notes_trees = xmalloc((display_notes_refs.nr+1)
+				      * sizeof(struct notes_tree *));
+	for_each_string_list(load_one_display_note_ref,
+			     &display_notes_refs, &cb_counter);
+	display_notes_trees[cb_counter] = NULL;
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,7 +1070,12 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
+	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
+		const char *ref = t->ref;
+		if (!prefixcmp(ref, "refs/"))
+			ref += 5;
+		strbuf_addf(sb, "\nNotes from %s:\n", ref);
+	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
 		strbuf_addstr(sb, "\nNotes:\n");
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
@@ -1031,6 +1090,17 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	free(msg);
 }
 
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	if (!display_notes_trees)
+		init_display_notes();
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
+
 int copy_note(struct notes_tree *t,
 	      const unsigned char *from_obj, const unsigned char *to_obj,
 	      int force, combine_notes_fn combine_fn)
diff --git a/notes.h b/notes.h
index b7547bf..f6a5229 100644
--- a/notes.h
+++ b/notes.h
@@ -193,6 +193,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 /* Flags controlling how notes are formatted */
 #define NOTES_SHOW_HEADER 1
 #define NOTES_INDENT 2
+#define NOTES_SHOW_HEADER_WITH_REF 4
 
 /*
  * Fill the given strbuf with the notes associated with the given object.
diff --git a/pretty.c b/pretty.c
index f999485..c185fd2 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);
 
 	free(reencoded);
 }
-- 
1.7.0.143.gbf2a7.dirty

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [WIP/RFC PATCH] Support showing notes from more than one notes tree
  2010-02-20 14:58           ` [WIP/RFC PATCH] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-20 15:23             ` Thomas Rast
  0 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 15:23 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

On Saturday 20 February 2010 15:58:47 Thomas Rast wrote:
> My actual intent was to allow more than one notes.displayref config
> setting, and loading all of them.  But this doesn't seem to work; a
> debugging print confirms that notes_display_config sees the entry, but
> another debugging print shows that it only ever loads one of them.
> What am I doing wrong?

Ah, figured that out myself: for_each_glob_ref has the logic of --glob
to attach /* if there are no globbing characters.  So I'll add some
code to skip for_each_ref and enter it in the list directly if there
is nothing to glob.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting
  2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
                     ` (10 preceding siblings ...)
  2010-02-16 23:26   ` [RFC PATCH v2 11/11] filter-branch: learn how to filter notes Thomas Rast
@ 2010-02-20 22:16   ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 01/12] Support showing notes from more than one notes tree Thomas Rast
                       ` (14 more replies)
  11 siblings, 15 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

> I'm running out of git time today

I should repeat that incantation more often, it mysteriously freed up
more time.

Brief overview of changes since v2, with some inline questions about
things that need to be settled:

* new [1/12] "Support showing notes from more than one notes tree" that
  teaches a "notes.displayRef" option which can glob for notes that
  should be shown, and can be specified more than once.

  - maybe we should default this to "refs/notes/*"?

  - currently git-notes and the configs don't agree on what is a valid
    notes ref.  git-notes refuses even *showing* outside of
    refs/notes/.  I tend towards changing git-notes at least as far as
    reading notes is concerned (show/list), so that remote notes work.

* dropped [5/11] "notes: clean up t3301" as per Junio's comments

* changed [7/12] "notes: implement helpers needed for note copying
  during rewrite" to support "notes.rewriteRef" as announced earlier.
  "notes.rewrite.<cmd>" now default to true, so that you only need to
  enable one, namely set notes.rewriteRef = refs/notes/*.

  - do we want a default?

  - do we want the rewriteRefs to also implicitly be displayRefs?

  - the "notes.rewriteMode = overwrite for notes/volatile/*" hinted at
    earlier is missing.  Does anyone need that?

* new [12/12] "notes: add shorthand --ref to override GIT_NOTES_REF"


I haven't changed [10/12] & [11/12] "filter-branch..." yet, as I'm not
really sure what the best format of such a filter would be.

The existing code is probably a neat way of editing the notes, but it
creates a new commit to the notes ref for every rewritten commit.  If
I change the default --notes-filter to cope with multi-rewriting, this
means a new notes commit per rewritten commit.  Seems rather wasteful
just to salvage your notes across an otherwise unrelated
filter-branch.

So the obvious fix would seem to be a --post-rewrite addition that
just pipes through 'git notes copy --for-rewrite=filter-branch', but
that's again not very customizable short of reinventing the whole
thing.  Any ideas?


Thomas Rast (12):
  Support showing notes from more than one notes tree
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  filter-branch: invoke post-rewrite hook
  filter-branch: learn how to filter notes
  notes: add shorthand --ref to override GIT_NOTES_REF

 Documentation/config.txt            |   40 +++++++-
 Documentation/git-filter-branch.txt |   20 +++-
 Documentation/git-notes.txt         |   20 +++-
 Documentation/githooks.txt          |   40 +++++++
 builtin-commit.c                    |   49 ++++++++
 builtin-notes.c                     |  168 +++++++++++++++++++++++++++-
 builtin.h                           |   16 +++
 git-am.sh                           |   13 ++
 git-filter-branch.sh                |   37 ++++++
 git-rebase--interactive.sh          |   45 +++++++-
 git-rebase.sh                       |    6 +
 notes.c                             |  125 +++++++++++++++++++-
 notes.h                             |   16 +++
 pretty.c                            |    6 +-
 refs.c                              |    4 +-
 refs.h                              |    5 +
 t/t3301-notes.sh                    |  213 ++++++++++++++++++++++++++++++++--
 t/t3306-notes-prune.sh              |    6 +-
 t/t3400-rebase.sh                   |   17 +++
 t/t3404-rebase-interactive.sh       |   11 ++
 t/t5407-post-rewrite-hook.sh        |  188 ++++++++++++++++++++++++++++++
 t/t7003-filter-branch.sh            |   22 ++++
 t/t7501-commit.sh                   |   12 ++
 23 files changed, 1046 insertions(+), 33 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 01/12] Support showing notes from more than one notes tree
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-21  3:06       ` Junio C Hamano
  2010-02-20 22:16     ` [RFC PATCH v3 02/12] Documentation: document post-rewrite hook Thomas Rast
                       ` (13 subsequent siblings)
  14 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/config.txt |   13 +++++-
 notes.c                  |  104 +++++++++++++++++++++++++++++++++++++++++++---
 notes.h                  |    7 +++
 pretty.c                 |    6 +-
 refs.c                   |    4 +-
 refs.h                   |    5 ++
 t/t3301-notes.sh         |   65 +++++++++++++++++++++++------
 t/t3306-notes-prune.sh   |    6 +-
 8 files changed, 181 insertions(+), 29 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 4c36aa9..3c5cfec 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -517,7 +517,7 @@ core.notesRef::
 	after the full SHA-1 of the commit they annotate.
 +
 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
+appended to the commit message, separated by a "Notes from <refname>:" line.  If the
 given ref itself does not exist, it is not an error, but means that no
 notes should be printed.
 +
@@ -1300,6 +1300,17 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+	When showing commit messages, also show notes which are stored
+	in the given ref.  The ref may also be a glob, in which case
+	notes from all matching refs will be shown.  You may also
+	specify this configuration variable several times.  If a ref
+	does not exist, it will be skipped silently.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/notes.c b/notes.c
index 3ba3e6d..d37178b 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+struct string_list display_notes_refs;
+struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,44 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+				   int flag, void *cb)
+{
+	struct string_list *refs = cb;
+	string_list_insert(path, refs);
+	return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+	if (has_glob_specials(glob))
+		for_each_glob_ref(string_list_add_one_ref, glob, list);
+	else
+		string_list_insert(glob, list);
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	if (!strcmp(k, "notes.displayref")) {
+		string_list_add_refs_by_glob(&display_notes_refs, v);
+		return 0;
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref()
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +883,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +907,43 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+struct load_notes_cb_data
+{
+	int counter;
+	struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	struct load_notes_cb_data *c = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	c->trees[c->counter++] = t;
+	return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+	struct notes_tree **trees;
+	struct load_notes_cb_data cb_data;
+	trees = xmalloc((display_notes_refs.nr+1) * sizeof(struct notes_tree *));
+	cb_data.counter = 0;
+	cb_data.trees = trees;
+	for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+	trees[cb_data.counter] = NULL;
+	return trees;
+}
+
+void init_display_notes()
+{
+	display_notes_refs.strdup_strings = 1;
+	string_list_insert(default_notes_ref(), &display_notes_refs);
+	git_config(notes_display_config, NULL);
+	display_notes_trees = load_notes_trees(&display_notes_refs);
+	string_list_clear(&display_notes_refs, 0);
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,7 +1092,12 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
+	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
+		const char *ref = t->ref;
+		if (!prefixcmp(ref, "refs/"))
+			ref += 5;
+		strbuf_addf(sb, "\nNotes from %s:\n", ref);
+	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
 		strbuf_addstr(sb, "\nNotes:\n");
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
@@ -1030,3 +1111,14 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	if (!display_notes_trees)
+		init_display_notes();
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
diff --git a/notes.h b/notes.h
index bad03cc..32ae2f0 100644
--- a/notes.h
+++ b/notes.h
@@ -184,6 +184,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 /* Flags controlling how notes are formatted */
 #define NOTES_SHOW_HEADER 1
 #define NOTES_INDENT 2
+#define NOTES_SHOW_HEADER_WITH_REF 4
 
 /*
  * Fill the given strbuf with the notes associated with the given object.
@@ -198,4 +199,10 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 		struct strbuf *sb, const char *output_encoding, int flags);
 
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags);
+
+struct notes_tree **load_notes_trees(struct string_list *refs);
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
 #endif
diff --git a/pretty.c b/pretty.c
index f999485..c185fd2 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);
 
 	free(reencoded);
 }
diff --git a/refs.c b/refs.c
index 503a8c2..6634091 100644
--- a/refs.c
+++ b/refs.c
@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 {
 	struct strbuf real_pattern = STRBUF_INIT;
 	struct ref_filter filter;
-	const char *has_glob_specials;
 	int ret;
 
 	if (!prefix && prefixcmp(pattern, "refs/"))
@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 		strbuf_addstr(&real_pattern, prefix);
 	strbuf_addstr(&real_pattern, pattern);
 
-	has_glob_specials = strpbrk(pattern, "?*[");
-	if (!has_glob_specials) {
+	if (!has_glob_specials(pattern)) {
 		/* Append impiled '/' '*' if not present. */
 		if (real_pattern.buf[real_pattern.len - 1] != '/')
 			strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9..4a18b08 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ struct ref_lock {
 extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
 extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
+static inline const char *has_glob_specials(const char *pattern)
+{
+	return strpbrk(pattern, "?*[");
+}
+
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 90178f9..0dfff82 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -98,7 +98,7 @@ Date:   Thu Apr 7 15:14:13 2005 -0700
 
     2nd
 
-Notes:
+Notes from notes/commits:
     b1
 EOF
 
@@ -125,7 +125,7 @@ Date:   Thu Apr 7 15:15:13 2005 -0700
 
     3rd
 
-Notes:
+Notes from notes/commits:
     b3
     c3c3c3c3
     d3d3d3
@@ -154,7 +154,7 @@ Date:   Thu Apr 7 15:16:13 2005 -0700
 
     4th
 
-Notes:
+Notes from notes/commits:
     xyzzy
 EOF
 
@@ -182,7 +182,7 @@ test_expect_success 'git log --pretty=raw does not show notes' '
 
 cat >>expect <<EOF
 
-Notes:
+Notes from notes/commits:
     xyzzy
 EOF
 test_expect_success 'git log --show-notes' '
@@ -237,7 +237,7 @@ Date:   Thu Apr 7 15:17:13 2005 -0700
 
     5th
 
-Notes:
+Notes from notes/commits:
     spam
 $whitespace
     foo
@@ -415,7 +415,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700
 
     6th
 
-Notes:
+Notes from notes/other:
     other note
 EOF
 
@@ -448,7 +448,46 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 	test_cmp expect-not-other output
 '
 
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes from notes/other:
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	git config --unset core.notesRef &&
+	git config notes.displayRef "refs/notes/*" &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+	git config core.notesRef refs/notes/other &&
+	git config notes.displayRef refs/notes/commits &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+	git config --unset core.notesRef &&
+	git config --add notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+	git config core.notesRef refs/notes/other &&
 	echo "Note on a tree" > expect
 	git notes add -m "Note on a tree" HEAD: &&
 	git notes show HEAD: > actual &&
@@ -472,7 +511,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700
 
     7th
 
-Notes:
+Notes from notes/other:
     other note
 EOF
 
@@ -503,7 +542,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes from notes/other:
     yet another note
 EOF
 
@@ -533,7 +572,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes from notes/other:
     yet another note
 $whitespace
     yet another note
@@ -552,7 +591,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes from notes/other:
     other note
 EOF
 
@@ -569,7 +608,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes from notes/other:
     other note
 $whitespace
     yet another note
@@ -588,7 +627,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes from notes/other:
     other note
 $whitespace
     yet another note
@@ -619,7 +658,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes from notes/other:
     yet another note
 $whitespace
     yet another note
diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh
index a0ed035..24dd503 100755
--- a/t/t3306-notes-prune.sh
+++ b/t/t3306-notes-prune.sh
@@ -30,7 +30,7 @@ Date:   Thu Apr 7 15:15:13 2005 -0700
 
     3rd
 
-Notes:
+Notes from notes/commits:
     Note #3
 
 commit 08341ad9e94faa089d60fd3f523affb25c6da189
@@ -39,7 +39,7 @@ Date:   Thu Apr 7 15:14:13 2005 -0700
 
     2nd
 
-Notes:
+Notes from notes/commits:
     Note #2
 
 commit ab5f302035f2e7aaf04265f08b42034c23256e1f
@@ -48,7 +48,7 @@ Date:   Thu Apr 7 15:13:13 2005 -0700
 
     1st
 
-Notes:
+Notes from notes/commits:
     Note #1
 END_OF_LOG
 
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 02/12] Documentation: document post-rewrite hook
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 01/12] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 03/12] commit --amend: invoke " Thomas Rast
                       ` (12 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/githooks.txt |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..41895e9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,42 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase', 'git-filter-branch').  Its first argument
+denotes the command it was invoked by: currently one of `amend`,
+`rebase`, or `filter-branch`.  Further command-dependent arguments may
+be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
+
+filter-branch::
+	Commits that were processed by 'git-filter-branch', but not
+	changed, are not included in the list.  If the list is empty
+	after this filtering, the hook is not invoked at all.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 03/12] commit --amend: invoke post-rewrite hook
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 01/12] Support showing notes from more than one notes tree Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 02/12] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-21  3:12       ` Junio C Hamano
  2010-02-20 22:16     ` [RFC PATCH v3 04/12] rebase: " Thomas Rast
                       ` (11 subsequent siblings)
  14 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-commit.c             |   43 ++++++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin-commit.c b/builtin-commit.c
index 55676fd..9b67649 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		struct commit *commit;
+		commit = lookup_commit(head_sha1);
+		if (!commit || parse_commit(commit))
+			die("HEAD commit disappeared right under my eyes?");
+		run_rewrite_hook(commit->object.sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd14..5735859 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -445,7 +445,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 04/12] rebase: invoke post-rewrite hook
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (2 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 03/12] commit --amend: invoke " Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 05/12] rebase -i: " Thomas Rast
                       ` (10 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 3c08d53..56428b4 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -575,6 +575,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -582,6 +583,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -768,6 +770,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -776,6 +782,10 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 git gc --auto
 
 rm -fr "$dotest"
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 05/12] rebase -i: invoke post-rewrite hook
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (3 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 04/12] rebase: " Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin' Thomas Rast
                       ` (9 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-rebase--interactive.sh   |   39 +++++++++++++++++++++
 t/t5407-post-rewrite-hook.sh |   78 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 0 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5735859..fc4f7e9 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/conflicted-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -446,6 +476,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -454,6 +485,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		make_patch $sha1
+		record_in_rewritten "$sha1"
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
 		warn "You can amend the commit now, with"
@@ -509,6 +541,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -685,6 +723,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/conflicted-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..488d4a0 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,82 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin'
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (4 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 05/12] rebase -i: " Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-21  3:31       ` Junio C Hamano
  2010-02-20 22:16     ` [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite Thomas Rast
                       ` (8 subsequent siblings)
  14 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-notes.txt |   12 ++++++++-
 builtin-notes.c             |   57 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   21 ++++++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   32 ++++++++++++++++++++++++
 5 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 14f73b9..f67cb6a 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -55,6 +55,16 @@ copy::
 	objects has none. (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin-notes.c b/builtin-notes.c
index 123ecad..62f6ceb 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,6 +278,47 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	struct notes_rewrite_cfg *c = NULL;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	free_notes(t);
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -287,7 +328,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -301,6 +342,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -349,8 +391,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				return notes_copy_from_stdin(force);
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index d37178b..b194870 100644
--- a/notes.c
+++ b/notes.c
@@ -1122,3 +1122,24 @@ void format_display_notes(const unsigned char *object_sha1,
 		format_note(display_notes_trees[i], object_sha1, sb,
 			    output_encoding, flags);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note;
+
+	note = get_note(t, from_obj);
+	if (!force) {
+		const unsigned char *existing_note = get_note(t, to_obj);
+		if (existing_note)
+			return 1;
+	}
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else
+		remove_note(t, to_obj);
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index 32ae2f0..fb09537 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 0dfff82..634d213 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -683,4 +683,36 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes from notes/other:
+    yet another note
+$whitespace
+    yet another note
+
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes from notes/other:
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	strip_then_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (5 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-21  3:34       ` Junio C Hamano
  2010-02-20 22:16     ` [RFC PATCH v3 08/12] rebase: support automatic notes copying Thomas Rast
                       ` (7 subsequent siblings)
  14 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/config.txt    |   23 ++++++++
 Documentation/git-notes.txt |    4 ++
 Documentation/githooks.txt  |    4 ++
 builtin-notes.c             |  121 ++++++++++++++++++++++++++++++++++++++++---
 builtin.h                   |   16 ++++++
 t/t3301-notes.sh            |  118 +++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 277 insertions(+), 9 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 3c5cfec..9c3b337 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1311,6 +1311,29 @@ The effective value of "core.notesRef" (possibly overridden by
 GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `true`, but see
+	"notes.rewriteRef" below.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
+
+notes.rewriteRef::
+	When copying notes during a rewrite, specifies the ref whose
+	notes should be copied.  The ref may be a glob, in which case
+	notes in all matching refs will be copied.  You may also
+	specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index f67cb6a..92f1249 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -34,6 +34,10 @@ 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".
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 41895e9..c33a38e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin-notes.c b/builtin-notes.c
index 62f6ceb..19dd3ab 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -16,6 +16,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static const char * const git_notes_usage[] = {
 	"git notes [list [<object>]]",
@@ -278,15 +279,104 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!strcmp(k, "notes.rewritemode")) {
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	} else if (!strcmp(k, "notes.rewriteref")) {
+		/* note that a refs/ prefix is implied in the
+		 * underlying for_each_glob_ref */
+		if (!prefixcmp(v, "refs/notes/") || !prefixcmp(v, "notes/"))
+			string_list_add_refs_by_glob(c->refs, v);
+		else
+			warning("Refusing to rewrite notes in %s"
+				" (outside of refs/notes/)", v);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	c->cmd = cmd;
+	c->enabled = 1;
+	c->combine = combine_notes_concatenate;
+	c->refs = xcalloc(1, sizeof(struct string_list));
+	c->refs->strdup_strings = 1;
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled || !c->refs->nr) {
+		string_list_clear(c->refs, 0);
+		free(c->refs);
+		free(c);
+		return NULL;
+	}
+	c->trees = load_notes_trees(c->refs);
+	string_list_clear(c->refs, 0);
+	free(c->refs);
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	int ret = 0;
+	int i;
+	for (i = 0; c->trees[i]; i++)
+		ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+	return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	int i;
+	for (i = 0; c->trees[i]; i++) {
+		commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+		free_notes(c->trees[i]);
+	}
+	free(c->trees);
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
 	struct notes_tree *t;
 	struct notes_rewrite_cfg *c = NULL;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -303,7 +393,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -314,8 +408,12 @@ int notes_copy_from_stdin(int force)
 		strbuf_list_free(split);
 	}
 
-	commit_notes(t, "Notes added by 'git notes copy'");
-	free_notes(t);
+	if (!rewrite_cmd) {
+		commit_notes(t, "Notes added by 'git notes copy'");
+		free_notes(t);
+	} else {
+		finish_copy_notes_for_rewrite(c);
+	}
 	return ret;
 }
 
@@ -331,6 +429,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -343,6 +442,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -391,6 +492,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -398,12 +503,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				return notes_copy_from_stdin(force);
+				return notes_copy_from_stdin(force, rewrite_cmd);
 			}
 		}
 		if (argc < 3) {
diff --git a/builtin.h b/builtin.h
index cdf9847..1508db6 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,22 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree **trees;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+	struct string_list *refs;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 634d213..5a0dccd 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -684,6 +684,7 @@ test_expect_success 'cannot copy note from object without notes' '
 '
 
 cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:25:13 2005 -0700
 
@@ -694,6 +695,7 @@ Notes from notes/other:
 $whitespace
     yet another note
 
+commit 7038787dfe22a14c3867ce816dbba39845359719
 Author: A U Thor <author@example.com>
 Date:   Thu Apr 7 15:24:13 2005 -0700
 
@@ -710,9 +712,123 @@ test_expect_success 'git notes copy --stdin' '
 	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
 	git notes copy --stdin &&
 	git log -2 > output &&
-	strip_then_cmp expect output &&
+	test_cmp expect output &&
 	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes from notes/other:
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes from notes/other:
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewriteMode overwrite &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+	git config notes.rewrite.bar false &&
+	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=bar &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes from notes/other:
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes from notes/other:
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 08/12] rebase: support automatic notes copying
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (6 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 09/12] commit --amend: copy notes to the new commit Thomas Rast
                       ` (6 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   17 +++++++++++++++++
 t/t3404-rebase-interactive.sh |   11 +++++++++++
 5 files changed, 37 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 56428b4..83cf6e7 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -782,8 +782,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 git gc --auto
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index fc4f7e9..1fda620 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -570,6 +570,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..dbf7dfb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..a5c5008 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,15 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 09/12] commit --amend: copy notes to the new commit
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (7 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 08/12] rebase: support automatic notes copying Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 10/12] filter-branch: invoke post-rewrite hook Thomas Rast
                       ` (5 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 builtin-commit.c  |    6 ++++++
 t/t7501-commit.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 9b67649..1943615 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -1341,9 +1341,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
 		struct commit *commit;
+		struct notes_rewrite_cfg *cfg;
 		commit = lookup_commit(head_sha1);
 		if (!commit || parse_commit(commit))
 			die("HEAD commit disappeared right under my eyes?");
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, commit->object.sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(commit->object.sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..8297cb4 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 10/12] filter-branch: invoke post-rewrite hook
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (8 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 09/12] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 11/12] filter-branch: learn how to filter notes Thomas Rast
                       ` (4 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

With a twist: since this is supposed to be the be-all end-all of
rewriting, we give the user the chance to override the hook.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-filter-branch.txt |    7 ++++++-
 git-filter-branch.sh                |   21 +++++++++++++++++++++
 t/t5407-post-rewrite-hook.sh        |   28 ++++++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 1 deletions(-)

diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 020028c..28a705f 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -12,7 +12,7 @@ SYNOPSIS
 	[--index-filter <command>] [--parent-filter <command>]
 	[--msg-filter <command>] [--commit-filter <command>]
 	[--tag-name-filter <command>] [--subdirectory-filter <directory>]
-	[--prune-empty]
+	[--prune-empty] [--post-rewrite <command>]
 	[--original <namespace>] [-d <directory>] [-f | --force]
 	[--] [<rev-list options>...]
 
@@ -182,6 +182,11 @@ the nearest ancestor that was not excluded.
 	of the `git commit-tree "$@"` idiom in your commit filter to make that
 	happen.
 
+--post-rewrite <command>::
+	Overrides the post-rewrite hook (if you have one).  To bypass
+	the hook for this invocation, pass `:`.  See
+	linkgit:githooks[1].
+
 --original <namespace>::
 	Use this option to set the namespace where the original commits
 	will be stored. The default value is 'refs/original'.
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 88fb0f0..301c497 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -101,6 +101,7 @@ USAGE="[--env-filter <command>] [--tree-filter <command>]
             [--index-filter <command>] [--parent-filter <command>]
             [--msg-filter <command>] [--commit-filter <command>]
             [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+	    [--post-rewrite <command>]
             [--original <namespace>] [-d <directory>] [-f | --force]
             [<rev-list options>...]"
 
@@ -126,6 +127,13 @@ orig_namespace=refs/original/
 force=
 prune_empty=
 remap_to_ancestor=
+
+if test -x "$GIT_DIR"/hooks/post-rewrite; then
+	post_rewrite=$(git rev-parse --sq-quote "$GIT_DIR"/hooks/post-rewrite)
+else
+	post_rewrite=:
+fi
+
 while :
 do
 	case "$1" in
@@ -190,6 +198,10 @@ do
 		filter_subdir="$OPTARG"
 		remap_to_ancestor=t
 		;;
+	--post-rewrite)
+		post_rewrite="$OPTARG"
+		post_rewrite_given=t
+		;;
 	--original)
 		orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
 		;;
@@ -358,6 +370,10 @@ while read commit parents; do
 	@SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
 		$(git write-tree) $parentstr < ../message > ../map/$commit ||
 			die "could not write rewritten commit"
+	new_commit=$(map $commit)
+	if test $commit != $new_commit; then
+		echo $commit $new_commit >> "$workdir"/../rewritten
+	fi
 done <../revs
 
 # If we are filtering for paths, as in the case of a subdirectory
@@ -484,6 +500,11 @@ if [ "$filter_tag_name" ]; then
 fi
 
 cd ../..
+
+if test -s "$workdir"/../rewritten; then
+	eval "$post_rewrite" 'filter-branch < "$workdir"/../rewritten'
+fi
+
 rm -rf "$tempdir"
 
 trap - 0
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 488d4a0..fa81fd4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -157,4 +157,32 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git filter-branch' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f --tree-filter "touch newfile" B..HEAD &&
+	echo filter-branch >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git filter-branch (no-op)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f B..HEAD &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_expect_success 'git filter-branch --post-rewrite :' '
+	git reset --hard D &&
+	clear_hook_input &&
+	git filter-branch -f --post-rewrite : --tree-filter "touch newfile" B..HEAD &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
 test_done
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 11/12] filter-branch: learn how to filter notes
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (9 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 10/12] filter-branch: invoke post-rewrite hook Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-20 22:16     ` [RFC PATCH v3 12/12] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
                       ` (3 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teach filter-branch a new option --notes-filter that acts as a pipe
filter (much like --msg-filter) for the commit notes.  For consistency
with the note copying support in amend/rebase, it defaults to 'cat' if
notes.rewrite.filter-branch is set.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/config.txt            |    8 ++++++--
 Documentation/git-filter-branch.txt |   13 +++++++++++++
 Documentation/githooks.txt          |    8 ++++----
 git-filter-branch.sh                |   18 +++++++++++++++++-
 t/t7003-filter-branch.sh            |   22 ++++++++++++++++++++++
 5 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 9c3b337..5e1d734 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1312,8 +1312,8 @@ GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
 notes.rewrite.<command>::
-	When rewriting commits with <command> (currently `amend` or
-	`rebase`) and this variable is set to `true`, git
+	When rewriting commits with <command> (currently `amend`,
+	`rebase` or `filter-branch`) and this variable is set to `true`, git
 	automatically copies your notes from the original to the
 	rewritten commit.  Defaults to `true`, but see
 	"notes.rewriteRef" below.
@@ -1324,6 +1324,10 @@ notes.rewriteMode::
 	the target commit already has a note.  Must be one of
 	`overwrite`, `concatenate`, or `ignore`.  Defaults to
 	`concatenate`.
++
+Note that 'git-filter-branch' ignores this setting and always
+overwrites; see the description of `--notes-filter` in
+linkgit:git-filter-branch[1].
 
 notes.rewriteRef::
 	When copying notes during a rewrite, specifies the ref whose
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 28a705f..6fd97d7 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -161,6 +161,19 @@ to other tags will be rewritten to point to the underlying commit.
 	The result will contain that directory (and only that) as its
 	project root.  Implies --remap-to-ancestor.
 
+--notes-filter <command>::
+	This filter rewrites the notes (see linkgit:git-notes[1]).  It
+	gets the old notes on standard input, and its standard output
+	is written as the new note (overwriting any existing notes).
++
+Defaults to doing nothing, unless the `notes.rewrite.filter-branch`
+option is set, in which case it copies the notes (i.e., defaults to
+`cat`).
++
+In addition to the usual variables, GIT_NEW_COMMIT is set to the
+result of the rewriting so that the filter can get at the existing
+notes, if any.
+
 --remap-to-ancestor::
 	Rewrite refs to the nearest rewritten ancestor instead of
 	ignoring them.
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index c33a38e..f9252dd 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -350,11 +350,11 @@ rebase::
 filter-branch::
 	Commits that were processed by 'git-filter-branch', but not
 	changed, are not included in the list.  If the list is empty
-	after this filtering, the hook is not invoked at all.
+	after this filtering, the hook is not invoked at all.  Also
+	see the `--post-rewrite` option in
+	linkgit:git-filter-branch[1].
 
-There is no default 'post-rewrite' hook, but see the
-`post-receive-copy-notes` script in `contrib/hooks` for an example
-that copies your git-notes to the rewritten commits.
+There is no default 'post-rewrite' hook.
 
 
 GIT
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 301c497..ad2da4e 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -101,7 +101,7 @@ USAGE="[--env-filter <command>] [--tree-filter <command>]
             [--index-filter <command>] [--parent-filter <command>]
             [--msg-filter <command>] [--commit-filter <command>]
             [--tag-name-filter <command>] [--subdirectory-filter <directory>]
-	    [--post-rewrite <command>]
+	    [--post-rewrite <command>] [--notes-filter <command>]
             [--original <namespace>] [-d <directory>] [-f | --force]
             [<rev-list options>...]"
 
@@ -134,6 +134,12 @@ else
 	post_rewrite=:
 fi
 
+if test "$(git config --bool notes.rewrite.filter-branch)"; then
+	filter_notes=cat
+else
+	filter_notes=
+fi
+
 while :
 do
 	case "$1" in
@@ -198,6 +204,9 @@ do
 		filter_subdir="$OPTARG"
 		remap_to_ancestor=t
 		;;
+	--notes-filter)
+		filter_notes="$OPTARG"
+		;;
 	--post-rewrite)
 		post_rewrite="$OPTARG"
 		post_rewrite_given=t
@@ -374,6 +383,13 @@ while read commit parents; do
 	if test $commit != $new_commit; then
 		echo $commit $new_commit >> "$workdir"/../rewritten
 	fi
+	if test -n "$filter_notes"; then
+		if git notes show $commit >../note 2>/dev/null; then
+			export GIT_NEW_COMMIT="$new_commit"
+			eval "$filter_notes" <../note |
+			git notes add -F- -f $new_commit
+		fi
+	fi
 done <../revs
 
 # If we are filtering for paths, as in the case of a subdirectory
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 0da13a8..6dc21e8 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -306,6 +306,28 @@ test_expect_success '--remap-to-ancestor with filename filters' '
 	test $orig_invariant = $(git rev-parse invariant)
 '
 
+test_expect_success '--notes-filter off' '
+	git checkout master &&
+	test_commit notes-foo &&
+	git notes add -mtestnote &&
+	git filter-branch -f --tree-filter "touch notes-1" HEAD^.. &&
+	test_must_fail git notes show
+'
+
+test_expect_success '--notes-filter manually' '
+	git reset --hard notes-foo &&
+	git filter-branch -f --notes-filter "sed s/test/foo/" \
+		--tree-filter "touch notes-2" HEAD^.. &&
+	test foonote = "$(git notes show)"
+'
+
+test_expect_success '--notes-filter implicit' '
+	git reset --hard notes-foo &&
+	git config notes.rewrite.filter-branch true &&
+	git filter-branch -f --tree-filter "touch notes-3" HEAD^.. &&
+	test testnote = "$(git notes show)"
+'
+
 test_expect_success 'setup submodule' '
 	rm -fr ?* .git &&
 	git init &&
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [RFC PATCH v3 12/12] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (10 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 11/12] filter-branch: learn how to filter notes Thomas Rast
@ 2010-02-20 22:16     ` Thomas Rast
  2010-02-21  3:47     ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Junio C Hamano
                       ` (2 subsequent siblings)
  14 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-20 22:16 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
hence determines the notes tree that will be manipulated.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-notes.txt |    4 ++++
 builtin-notes.c             |    6 ++++++
 2 files changed, 10 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 92f1249..dcdcc95 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -115,6 +115,10 @@ OPTIONS
 	Like '-C', but with '-c' the editor is invoked, so that
 	the user can further edit the note message.
 
+--ref <ref>::
+	Manipulate the notes tree in <ref>.  This overrides both
+	GIT_NOTES_REF and the "core.notesRef" configuration.
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/builtin-notes.c b/builtin-notes.c
index 19dd3ab..8cc0037 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -430,6 +430,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	const char *rewrite_cmd = NULL;
+	const char *override_notes_ref = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -442,6 +443,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
 		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
@@ -451,6 +454,9 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 
+	if (override_notes_ref)
+		setenv("GIT_NOTES_REF", override_notes_ref, 1);
+
 	if (argc && !strcmp(argv[0], "list"))
 		list = 1;
 	else if (argc && !strcmp(argv[0], "add"))
-- 
1.7.0.59.g783f8

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 01/12] Support showing notes from more than one notes tree
  2010-02-20 22:16     ` [RFC PATCH v3 01/12] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-21  3:06       ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-21  3:06 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 4c36aa9..3c5cfec 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -517,7 +517,7 @@ core.notesRef::
>  	after the full SHA-1 of the commit they annotate.
>  +
>  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
> +appended to the commit message, separated by a "Notes from <refname>:" line.  If the

I haven't looked at your code, but I think the output should be kept the
same when <refname> is "refs/notes/commits", and it probably is a good
idea to omit "refs/notes/" part.  E.g. "Notes (amlog):" for notes stored
in the refs/notes/amlog hierarchy.

> @@ -1300,6 +1300,17 @@ mergetool.keepTemporaries::
>  mergetool.prompt::
>  	Prompt before each invocation of the merge resolution program.
>  
> +notes.displayRef::
> +	When showing commit messages, also show notes which are stored
> +	in the given ref.  The ref may also be a glob, in which case

If this were a description that follows an example

	[notes]
		displayRef = <ref>

then the above sentences make sense, but otherwise "in the given ref" does
not make much sense.  "The ref may also be a glob" is a lot worse. A "ref"
is never be a glob.  You can specify what refs to display by setting the
_value_ of this variable to a glob.

    notes.displayRef::
        The refname in the notes namespace from which to show notes when
        showing commit messages.  The value of this variable can be set to
        a glob, in whch case ...

> +	...  If a ref
> +	does not exist, it will be skipped silently.

Hmph...  Even when it is not a globbing specification?  If I said

	[notes]
		displayRef = refs/notes/tr/*

and there is no notes in the tr/ hierarchy yet, I would appreciate if the
code doesn't complain, but if I said a more concrete

	[notes]
		displayRef = refs/notes/anlog

and there is no "anlog", because I meant "amlog" and the above was a typo,
I would appreciate if the code somehow lets me know about it.

> +static int notes_display_config(const char *k, const char *v, void *cb)
> +{
> +	if (!strcmp(k, "notes.displayref")) {
> +		string_list_add_refs_by_glob(&display_notes_refs, v);

Somebody in the callchain should say

	if (!v)
        	config_error_nonbool(k)

before you use v here.

I don't think string_list_insert() has a desirable property for this.
If I wrote

	[notes]
            displayRef = refs/notes/commit
            displayRef = refs/notes/amlog

I would expect that the normal notes come first and then notes from the
amlog tree, not in the alphabetical "amlog happens to sort before commit"
order.  If I used a glob, e.g. "refs/notes/tr/*", I am saying "all notes
in the tr/ namespace, I do not care about the particular order among them.
Just use something sensible", and it would make sense to use some stable
ordering, e.g. the order for_each_glob_ref() calls you back, but receiving
the data and calling string_list_insert() will destroy the order the user
gave you the configuration, no?

> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 90178f9..0dfff82 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -98,7 +98,7 @@ Date:   Thu Apr 7 15:14:13 2005 -0700
>  
>      2nd
>  
> -Notes:
> +Notes from notes/commits:

Please don't.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 03/12] commit --amend: invoke post-rewrite hook
  2010-02-20 22:16     ` [RFC PATCH v3 03/12] commit --amend: invoke " Thomas Rast
@ 2010-02-21  3:12       ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-21  3:12 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> +static const char post_rewrite_hook[] = "hooks/post-rewrite";
> +
> +static int run_rewrite_hook(const unsigned char *oldsha1,
> +			    const unsigned char *newsha1)
> +{
> +...
> +}
> +
>  int cmd_commit(int argc, const char **argv, const char *prefix)
>  {
>  	struct strbuf sb = STRBUF_INIT;
> @@ -1303,6 +1339,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
>  
>  	rerere(0);
>  	run_hook(get_index_file(), "post-commit", NULL);
> +	if (amend && !no_post_rewrite) {
> +		struct commit *commit;
> +		commit = lookup_commit(head_sha1);
> +		if (!commit || parse_commit(commit))
> +			die("HEAD commit disappeared right under my eyes?");
> +		run_rewrite_hook(commit->object.sha1, commit_sha1);

Why aren't you passing head_sha1 to run_rewrite_hook() in the first place,
and instead run lookup_commit() only to get its object.sha1?

This is merely a "post" hook, so failure to run it should not cause the
main program to fail.  By not looking it up you do not even have to check
and call die().  Let the dying be done in the hook instead ;-).

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin'
  2010-02-20 22:16     ` [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-02-21  3:31       ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-21  3:31 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 0dfff82..634d213 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -683,4 +683,36 @@ test_expect_success 'cannot copy note from object without notes' '
> ...
> +test_expect_success 'git notes copy --stdin' '
> +	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
> +	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
> +	git notes copy --stdin &&
> +	git log -2 > output &&
> +	strip_then_cmp expect output &&

Huh?

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite
  2010-02-20 22:16     ` [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-21  3:34       ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-21  3:34 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> +combine_notes_fn *parse_combine_notes_fn(const char *v)
> +{
> +	if (!strcasecmp(v, "overwrite"))
> +		return combine_notes_overwrite;
> +	else if (!strcasecmp(v, "ignore"))
> +		return combine_notes_ignore;
> +	else if (!strcasecmp(v, "concatenate"))
> +		return combine_notes_concatenate;
> +	else
> +		return NULL;
> +}

Somebody in the call chain should have made sure v != NULL and called
config_error_nonbool() if v is NULL.

> diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
> index 634d213..5a0dccd 100755
> --- a/t/t3301-notes.sh
> +++ b/t/t3301-notes.sh
> @@ -684,6 +684,7 @@ test_expect_success 'cannot copy note from object without notes' '
>  '
>  
>  cat > expect << EOF
> +commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
>  Author: A U Thor <author@example.com>
>  Date:   Thu Apr 7 15:25:13 2005 -0700
>  
> @@ -694,6 +695,7 @@ Notes from notes/other:
>  $whitespace
>      yet another note
>  
> +commit 7038787dfe22a14c3867ce816dbba39845359719
>  Author: A U Thor <author@example.com>
>  Date:   Thu Apr 7 15:24:13 2005 -0700
>  
> @@ -710,9 +712,123 @@ test_expect_success 'git notes copy --stdin' '
>  	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
>  	git notes copy --stdin &&
>  	git log -2 > output &&
> -	strip_then_cmp expect output &&
> +	test_cmp expect output &&
>  	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
>  	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
>  '

I think the above piece should have been squashed to the previous patch.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (11 preceding siblings ...)
  2010-02-20 22:16     ` [RFC PATCH v3 12/12] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-02-21  3:47     ` Junio C Hamano
  2010-02-21  6:14       ` Thomas Rast
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
  14 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-21  3:47 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Brief overview of changes since v2, with some inline questions about
> things that need to be settled:
>
> * new [1/12] "Support showing notes from more than one notes tree" that
>   teaches a "notes.displayRef" option which can glob for notes that
>   should be shown, and can be specified more than once.
>
>   - maybe we should default this to "refs/notes/*"?

Probably not.

I like the general direction in which this series is going, but a config
that does not have any command line counterpart is a design mistake.

When adding a feature, always start from the command line option, and then
add configuration mechanism purely as a convenience (so that users do not
have to give the same option every time they run the command).  By forcing
yourself to stick to that design pattern, it is easier to ensure that any
new feature can be disabled by people who do not want it from the command
line.

> * new [12/12] "notes: add shorthand --ref to override GIT_NOTES_REF"

Hmm; if this will _always_ be limited to "git notes" command and no other
commands will _ever_ learn a command line option to override what notes
namespace to use in the future, then "--ref" is perfectly a good name.
But otherwise, "git log --ref=notes/amlog" would look _very wrong_, and
would end up being called "git log --notes-ref=notes/amlog" instead.  When
that happens, "git notes --ref=notes/amlog" would look inconsistent and
people will complain, no?

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting
  2010-02-21  3:47     ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Junio C Hamano
@ 2010-02-21  6:14       ` Thomas Rast
  2010-02-22  0:18         ` Junio C Hamano
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-21  6:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

On Sunday 21 February 2010 04:47:37 Junio C Hamano wrote:
> 
> I like the general direction in which this series is going, but a config
> that does not have any command line counterpart is a design mistake.

I hate[1] to point out that according to the same criterion, the
*current* notes are a design mistake.  Unless you count GIT_NOTES_REF,
in which case in my opinion...

> Hmm; if this will _always_ be limited to "git notes" command and no other
> commands will _ever_ learn a command line option to override what notes
> namespace to use in the future, then "--ref" is perfectly a good name.
> But otherwise, "git log --ref=notes/amlog" would look _very wrong_, and
> would end up being called "git log --notes-ref=notes/amlog" instead.  When
> that happens, "git notes --ref=notes/amlog" would look inconsistent and
> people will complain, no?

... the obvious extension of the same would be GIT_NOTES_DISPLAY_REF
(colon separated list of globs), GIT_NOTES_REWRITE_REF,
GIT_NOTES_REWRITE_MODE, and whatnot.

Which could still be made into a "global" option along the same lines
as GIT_WORK_TREE.

I don't really want to go the other way and patch every command that
could conceivably have notes in its output.


[1] No I don't, that just sounded nice.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (12 preceding siblings ...)
  2010-02-21  3:47     ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Junio C Hamano
@ 2010-02-22  0:10     ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
                         ` (11 more replies)
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
  14 siblings, 12 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Thanks Junio for your big batch of feedback.  Yet another round...

Since this topic already has three different goals, I decided to try
and get it in at least 'pu' shapes to have an agreed-upon basis for
what's next.  To that end, I ejected the filter-branch commits for
now, and hopefully addressed all comments on the existing patches.  I
still need to make up my mind (and would still appreciate input) on
the filter-branch part, but if I'm not mistaken the rest of the series
has been on Junio's wishlist.  I also already have a patch to make
format-patch fill the comment region from a notes ref that has textual
dependencies on this, which IIRC has also been mentioned as a wishlist
item once or twice.

I used the entire series plus the format-patch feature mentioned above
fairly heavily during its own development, which unearthed a few more
bugs.  If there is a comment after the --- line in every patch, it
worked ;-)

On Sunday 21 February 2010 04:06:52 Junio C Hamano wrote:
> If I wrote
> 
> 	[notes]
>             displayRef = refs/notes/commit
>             displayRef = refs/notes/amlog
> 
> I would expect that the normal notes come first and then notes from the
> amlog tree, not in the alphabetical "amlog happens to sort before commit"
> order.  [v3 always sorted the displayRef entries for display]

I considered this a feature, but changed it anyway since I don't care
much.  My original rationale was: core.notesRef should be in that list
implicitly, and I think the natural position if it is unsorted would
be: first.  However, that means that changing core.notesRef or
GIT_NOTES_REF can shuffle around your notes, which I thought would be
confusing.


Thomas Rast (11):
  test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  Support showing notes from more than one notes tree
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  notes: add shorthand --ref to override GIT_NOTES_REF

 Documentation/config.txt      |   49 ++++++-
 Documentation/git-notes.txt   |   20 +++-
 Documentation/githooks.txt    |   40 +++++
 builtin-commit.c              |   45 ++++++
 builtin-notes.c               |  195 +++++++++++++++++++++++-
 builtin.h                     |   18 ++
 cache.h                       |    3 +
 git-am.sh                     |   13 ++
 git-rebase--interactive.sh    |   52 ++++++-
 git-rebase.sh                 |    6 +
 notes.c                       |  173 ++++++++++++++++++++-
 notes.h                       |   17 ++
 pretty.c                      |    6 +-
 refs.c                        |    4 +-
 refs.h                        |    5 +
 t/t3301-notes.sh              |  344 ++++++++++++++++++++++++++++++++++++++++-
 t/t3400-rebase.sh             |   17 ++
 t/t3404-rebase-interactive.sh |   24 +++
 t/t5407-post-rewrite-hook.sh  |  172 ++++++++++++++++++++
 t/t7501-commit.sh             |   12 ++
 t/test-lib.sh                 |    4 +
 21 files changed, 1194 insertions(+), 25 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 02/11] Support showing notes from more than one notes tree Thomas Rast
                         ` (10 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

---

New in this round.

 t/test-lib.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index afd3053..1e7eab1 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY
 unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 02/11] Support showing notes from more than one notes tree
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22 23:20         ` Junio C Hamano
  2010-02-22  0:10       ` [PATCH v4 03/11] Documentation: document post-rewrite hook Thomas Rast
                         ` (9 subsequent siblings)
  11 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.

Thanks to Junio C Hamano for lots of feedback, which greatly
influenced the design of the entire series and this commit in
particular.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v3:

* introduce GIT_NOTES_DISPLAY_REF

* the notes refs are kept in the order the user provided them

* the header is now 'Notes (refname):' or 'Notes:' in the case of
  refs/notes/commits

* fixes as per Junio's comments

 Documentation/config.txt |   19 ++++++-
 cache.h                  |    1 +
 notes.c                  |  153 ++++++++++++++++++++++++++++++++++++++++++++--
 notes.h                  |    7 ++
 pretty.c                 |    6 +-
 refs.c                   |    4 +-
 refs.h                   |    5 ++
 t/t3301-notes.sh         |  115 ++++++++++++++++++++++++++++++++---
 t/test-lib.sh            |    1 +
 9 files changed, 290 insertions(+), 21 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 4c36aa9..a0f2b0f 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -517,7 +517,7 @@ core.notesRef::
 	after the full SHA-1 of the commit they annotate.
 +
 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
+appended to the commit message, separated by a "Notes from <refname>:" line.  If the
 given ref itself does not exist, it is not an error, but means that no
 notes should be printed.
 +
@@ -1300,6 +1300,23 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+	The refname in the notes namespace from which to show notes
+	when showing commit messages.  The value of this variable can
+	be set to a glob, in which case notes from all matching refs
+	will be shown.  You may also specify this configuration
+	variable several times.  A warning will be issued for refs
+	that do not exist, but a glob that does not match any refs is
+	silently ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon (or semicolon) separated
+list of refs or globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/cache.h b/cache.h
index d478eff..445ce16 100644
--- a/cache.h
+++ b/cache.h
@@ -387,6 +387,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
diff --git a/notes.c b/notes.c
index 3ba3e6d..c480370 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+struct string_list display_notes_refs;
+struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,78 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+				   int flag, void *cb)
+{
+	struct string_list *refs = cb;
+	if (!unsorted_string_list_has_string(refs, path))
+		string_list_append(path, refs);
+	return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+	if (has_glob_specials(glob)) {
+		for_each_glob_ref(string_list_add_one_ref, glob, list);
+	} else {
+		unsigned char sha1[20];
+		if (get_sha1(glob, sha1))
+			warning("notes ref %s is invalid", glob);
+		if (!unsorted_string_list_has_string(list, glob))
+			string_list_append(glob, list);
+	}
+}
+
+void string_list_add_refs_from_glob_list(struct string_list *list,
+					 const char *globs)
+{
+	struct strbuf globbuf = STRBUF_INIT;
+	struct strbuf **split;
+	int i;
+
+	strbuf_addstr(&globbuf, globs);
+	split = strbuf_split(&globbuf, ':');
+
+	for (i = 0; split[i]; i++) {
+		if (!split[i]->len)
+			continue;
+		if (split[i]->buf[split[i]->len-1] == ':')
+			strbuf_setlen(split[i], split[i]->len-1);
+		string_list_add_refs_by_glob(list, split[i]->buf);
+	}
+
+	strbuf_list_free(split);
+	strbuf_release(&globbuf);
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	/* Warning!  This is currently not executed if
+	 * GIT_NOTES_DISPLAY_REF is set.  Move the git_config() call
+	 * outside the test if you add more options. */
+
+	if (!strcmp(k, "notes.displayref")) {
+		if (!v)
+			config_error_nonbool(k);
+		string_list_add_refs_by_glob(&display_notes_refs, v);
+		return 0;
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref()
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +917,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +941,52 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+struct load_notes_cb_data
+{
+	int counter;
+	struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	struct load_notes_cb_data *c = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	c->trees[c->counter++] = t;
+	return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+	struct notes_tree **trees;
+	struct load_notes_cb_data cb_data;
+	trees = xmalloc((display_notes_refs.nr+1) * sizeof(struct notes_tree *));
+	cb_data.counter = 0;
+	cb_data.trees = trees;
+	for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+	trees[cb_data.counter] = NULL;
+	return trees;
+}
+
+
+
+void init_display_notes()
+{
+	char *display_ref_env;
+	display_notes_refs.strdup_strings = 1;
+	string_list_append(default_notes_ref(), &display_notes_refs);
+	display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+	if (display_ref_env) {
+		string_list_add_refs_from_glob_list(&display_notes_refs,
+						    display_ref_env);
+	} else {
+		git_config(notes_display_config, NULL);
+	}
+	display_notes_trees = load_notes_trees(&display_notes_refs);
+	string_list_clear(&display_notes_refs, 0);
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,7 +1135,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
+	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
+		const char *ref = t->ref;
+		if (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+			strbuf_addstr(sb, "\nNotes:\n");
+		} else {
+			if (!prefixcmp(ref, "refs/"))
+				ref += 5;
+			if (!prefixcmp(ref, "notes/"))
+				ref += 6;
+			strbuf_addf(sb, "\nNotes (%s):\n", ref);
+		}
+	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
 		strbuf_addstr(sb, "\nNotes:\n");
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
@@ -1030,3 +1160,14 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	if (!display_notes_trees)
+		init_display_notes();
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
diff --git a/notes.h b/notes.h
index bad03cc..32ae2f0 100644
--- a/notes.h
+++ b/notes.h
@@ -184,6 +184,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 /* Flags controlling how notes are formatted */
 #define NOTES_SHOW_HEADER 1
 #define NOTES_INDENT 2
+#define NOTES_SHOW_HEADER_WITH_REF 4
 
 /*
  * Fill the given strbuf with the notes associated with the given object.
@@ -198,4 +199,10 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 		struct strbuf *sb, const char *output_encoding, int flags);
 
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags);
+
+struct notes_tree **load_notes_trees(struct string_list *refs);
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
 #endif
diff --git a/pretty.c b/pretty.c
index f999485..c185fd2 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);
 
 	free(reencoded);
 }
diff --git a/refs.c b/refs.c
index 503a8c2..6634091 100644
--- a/refs.c
+++ b/refs.c
@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 {
 	struct strbuf real_pattern = STRBUF_INIT;
 	struct ref_filter filter;
-	const char *has_glob_specials;
 	int ret;
 
 	if (!prefix && prefixcmp(pattern, "refs/"))
@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 		strbuf_addstr(&real_pattern, prefix);
 	strbuf_addstr(&real_pattern, pattern);
 
-	has_glob_specials = strpbrk(pattern, "?*[");
-	if (!has_glob_specials) {
+	if (!has_glob_specials(pattern)) {
 		/* Append impiled '/' '*' if not present. */
 		if (real_pattern.buf[real_pattern.len - 1] != '/')
 			strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9..4a18b08 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ struct ref_lock {
 extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
 extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
+static inline const char *has_glob_specials(const char *pattern)
+{
+	return strpbrk(pattern, "?*[");
+}
+
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 90178f9..e5c3de7 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -415,7 +415,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700
 
     6th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -448,7 +448,106 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 	test_cmp expect-not-other output
 '
 
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+
+Notes (other):
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	GIT_NOTES_REF=refs/notes/commits git notes add \
+		-m"replacement for deleted note" HEAD^ &&
+	GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+	git config --unset core.notesRef &&
+	git config notes.displayRef "refs/notes/*" &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+	git config core.notesRef refs/notes/commits &&
+	git config notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+	git config --unset core.notesRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git config --add notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+
+Notes:
+    order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+	git config core.notesRef refs/notes/other &&
+	git config --unset-all notes.displayRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git log -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+	git config --unset-all core.notesRef &&
+	git config --unset-all notes.displayRef &&
+	GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+		git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+	git config notes.displayRef "refs/notes/*" &&
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+	test_cmp expect-none output
+'
+
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+	git config core.notesRef refs/notes/other &&
 	echo "Note on a tree" > expect
 	git notes add -m "Note on a tree" HEAD: &&
 	git notes show HEAD: > actual &&
@@ -472,7 +571,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700
 
     7th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -503,7 +602,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 EOF
 
@@ -533,7 +632,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
@@ -552,7 +651,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -569,7 +668,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -588,7 +687,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -619,7 +718,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 1e7eab1..c814e9c 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -55,6 +55,7 @@ unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 03/11] Documentation: document post-rewrite hook
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 02/11] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 04/11] commit --amend: invoke " Thomas Rast
                         ` (8 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Unchanged from v3

 Documentation/githooks.txt |   36 ++++++++++++++++++++++++++++++++++++
 1 files changed, 36 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..41895e9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,42 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase', 'git-filter-branch').  Its first argument
+denotes the command it was invoked by: currently one of `amend`,
+`rebase`, or `filter-branch`.  Further command-dependent arguments may
+be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
+
+filter-branch::
+	Commits that were processed by 'git-filter-branch', but not
+	changed, are not included in the list.  If the list is empty
+	after this filtering, the hook is not invoked at all.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 04/11] commit --amend: invoke post-rewrite hook
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (2 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 03/11] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 05/11] rebase: " Thomas Rast
                         ` (7 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changed since v3: Use head_sha1 without resolving it again to find the
same sha1.  I must have been drunk when writing that detour.

 builtin-commit.c             |   39 +++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin-commit.c b/builtin-commit.c
index 55676fd..f476d85 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		run_rewrite_hook(head_sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd14..5735859 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -445,7 +445,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 05/11] rebase: invoke post-rewrite hook
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (3 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 04/11] commit --amend: invoke " Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 06/11] rebase -i: " Thomas Rast
                         ` (6 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Unchanged from v3

 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 3c08d53..56428b4 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -575,6 +575,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -582,6 +583,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -768,6 +770,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -776,6 +782,10 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 git gc --auto
 
 rm -fr "$dotest"
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 06/11] rebase -i: invoke post-rewrite hook
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (4 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 05/11] rebase: " Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
                         ` (5 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v3: Fixed a bug with the handling of 'edit' that
recorded the wrong commit.
* also handle the work done by skip_unneccessary_picks; not doing so
  meant that in a simple 'pick;squash' rebase, the 'pick' was not
  shown as rewritten to the squashed commit

 git-rebase--interactive.sh   |   46 +++++++++++++++++++++-
 t/t5407-post-rewrite-hook.sh |   90 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+), 1 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5735859..564f6ac 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/stopped-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -446,6 +476,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -453,6 +484,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		echo "$1" > "$DOTEST"/stopped-sha
 		make_patch $sha1
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
@@ -509,6 +541,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -571,7 +609,12 @@ skip_unnecessary_picks () {
 		esac
 		echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
 	done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
-	mv -f "$TODO".new "$TODO" ||
+	mv -f "$TODO".new "$TODO" &&
+	case "$(peek_next_command)" in
+	squash|s|fixup|f)
+		record_in_rewritten "$ONTO"
+		;;
+	esac ||
 	die "Could not skip unnecessary pick commands"
 }
 
@@ -685,6 +728,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..e1fccc4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,94 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 fixup 2" git rebase -i B &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 07/11] notes: implement 'git notes copy --stdin'
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (5 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 06/11] rebase -i: " Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
                         ` (4 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changed since v3: fixed the strip_then_cmp that I removed in the wrong
commit when going from v2 to v3.
* Fix bug in copy_note() that always removed the note when the source
  was empty, even for combine=concatenate or such.  A test for this is
  in the next commit, as with this one we always use combine=overwrite.

 Documentation/git-notes.txt |   12 ++++++++-
 builtin-notes.c             |   56 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   20 +++++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   34 ++++++++++++++++++++++++++
 5 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 14f73b9..f67cb6a 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -55,6 +55,16 @@ copy::
 	objects has none. (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin-notes.c b/builtin-notes.c
index 123ecad..daeb14e 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,6 +278,46 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	free_notes(t);
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -287,7 +327,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -301,6 +341,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -349,8 +390,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				return notes_copy_from_stdin(force);
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index c480370..d7215e2 100644
--- a/notes.c
+++ b/notes.c
@@ -1171,3 +1171,23 @@ void format_display_notes(const unsigned char *object_sha1,
 		format_note(display_notes_trees[i], object_sha1, sb,
 			    output_encoding, flags);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note = get_note(t, from_obj);
+	const unsigned char *existing_note = get_note(t, to_obj);
+
+	if (!force && existing_note)
+		return 1;
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else if (existing_note) {
+		add_note(t, to_obj, null_sha1, combine_fn);
+	} else
+		remove_note(t, to_obj);
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index 32ae2f0..fb09537 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index e5c3de7..40c17f1 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -743,4 +743,38 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	test_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 08/11] notes: implement helpers needed for note copying during rewrite
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (6 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 09/11] rebase: support automatic notes copying Thomas Rast
                         ` (3 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v3: introduced GIT_NOTES_REWRITE_REF and _MODE.
* more tests

 Documentation/config.txt    |   30 +++++++
 Documentation/git-notes.txt |    4 +
 Documentation/githooks.txt  |    4 +
 builtin-notes.c             |  139 +++++++++++++++++++++++++++++--
 builtin.h                   |   18 ++++
 cache.h                     |    2 +
 notes.h                     |    1 +
 t/t3301-notes.sh            |  195 +++++++++++++++++++++++++++++++++++++++++++
 t/test-lib.sh               |    2 +
 9 files changed, 387 insertions(+), 8 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index a0f2b0f..46adaf1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1317,6 +1317,36 @@ The effective value of "core.notesRef" (possibly overridden by
 GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `true`, but see
+	"notes.rewriteRef" below.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
+environment variable, which must be a colon (or semicolon) separated
+list of refs or globs.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+	When copying notes during a rewrite, specifies the ref whose
+	notes should be copied.  The ref may be a glob, in which case
+	notes in all matching refs will be copied.  You may also
+	specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index f67cb6a..92f1249 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -34,6 +34,10 @@ 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".
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 41895e9..c33a38e 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin-notes.c b/builtin-notes.c
index daeb14e..8c65709 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -16,6 +16,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static const char * const git_notes_usage[] = {
 	"git notes [list [<object>]]",
@@ -278,14 +279,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+		if (!v)
+			config_error_nonbool(k);
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	} else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+		/* note that a refs/ prefix is implied in the
+		 * underlying for_each_glob_ref */
+		if (!prefixcmp(v, "refs/notes/") || !prefixcmp(v, "notes/"))
+			string_list_add_refs_by_glob(c->refs, v);
+		else
+			warning("Refusing to rewrite notes in %s"
+				" (outside of refs/notes/)", v);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+	const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+	c->cmd = cmd;
+	c->enabled = 1;
+	c->combine = combine_notes_concatenate;
+	c->refs = xcalloc(1, sizeof(struct string_list));
+	c->refs->strdup_strings = 1;
+	c->refs_from_env = 0;
+	c->mode_from_env = 0;
+	if (rewrite_mode_env) {
+		c->mode_from_env = 1;
+		c->combine = parse_combine_notes_fn(rewrite_mode_env);
+		if (!c->combine)
+			error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
+			      " value: '%s'", rewrite_mode_env);
+	}
+	if (rewrite_refs_env) {
+		c->refs_from_env = 1;
+		string_list_add_refs_from_glob_list(c->refs, rewrite_refs_env);
+	}
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled || !c->refs->nr) {
+		string_list_clear(c->refs, 0);
+		free(c->refs);
+		free(c);
+		return NULL;
+	}
+	c->trees = load_notes_trees(c->refs);
+	string_list_clear(c->refs, 0);
+	free(c->refs);
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	int ret = 0;
+	int i;
+	for (i = 0; c->trees[i]; i++)
+		ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+	return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	int i;
+	for (i = 0; c->trees[i]; i++) {
+		commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+		free_notes(c->trees[i]);
+	}
+	free(c->trees);
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
+	struct notes_rewrite_cfg *c = NULL;
 	struct notes_tree *t;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -302,7 +410,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -313,8 +425,12 @@ int notes_copy_from_stdin(int force)
 		strbuf_list_free(split);
 	}
 
-	commit_notes(t, "Notes added by 'git notes copy'");
-	free_notes(t);
+	if (!rewrite_cmd) {
+		commit_notes(t, "Notes added by 'git notes copy'");
+		free_notes(t);
+	} else {
+		finish_copy_notes_for_rewrite(c);
+	}
 	return ret;
 }
 
@@ -330,6 +446,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -342,6 +459,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -390,6 +509,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -397,12 +520,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				return notes_copy_from_stdin(force);
+				return notes_copy_from_stdin(force, rewrite_cmd);
 			}
 		}
 		if (argc < 3) {
diff --git a/builtin.h b/builtin.h
index cdf9847..8aebe61 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,24 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree **trees;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+	struct string_list *refs;
+	int refs_from_env;
+	int mode_from_env;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 445ce16..ab740e3 100644
--- a/cache.h
+++ b/cache.h
@@ -388,6 +388,8 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
diff --git a/notes.h b/notes.h
index fb09537..1f48af3 100644
--- a/notes.h
+++ b/notes.h
@@ -213,5 +213,6 @@ void format_display_notes(const unsigned char *object_sha1,
 
 struct notes_tree **load_notes_trees(struct string_list *refs);
 void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+void string_list_add_refs_from_glob_list(struct string_list *list, const char *globs);
 
 #endif
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 40c17f1..34a993c 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -777,4 +777,199 @@ test_expect_success 'git notes copy --stdin' '
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewriteMode overwrite &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+	git config notes.rewrite.bar false &&
+	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=bar &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+    append 1
+    append 2
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+	git notes add -f -m"append 1" HEAD^ &&
+	git notes add -f -m"append 2" HEAD^^ &&
+	(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
+	echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
+	git notes remove HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 1
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+	git notes add -f -m"replacement note 1" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 2
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+	git config notes.rewriteMode overwrite &&
+	git notes add -f -m"replacement note 2" HEAD^ &&
+	git config --unset-all notes.rewriteRef &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
+		git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
+	git config notes.rewriteRef refs/notes/other &&
+	git notes add -f -m"replacement note 3" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index c814e9c..4caad9f 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
 unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 09/11] rebase: support automatic notes copying
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (7 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 10/11] commit --amend: copy notes to the new commit Thomas Rast
                         ` (2 subsequent siblings)
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changed from v3: more tests

 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   17 +++++++++++++++++
 t/t3404-rebase-interactive.sh |   24 ++++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 56428b4..83cf6e7 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -782,8 +782,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 git gc --auto
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 564f6ac..415ae72 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -570,6 +570,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..dbf7dfb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..19668c2 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,28 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+	git reset --hard n3 &&
+	git notes add -m"an earlier note" n2 &&
+	GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+	git notes show > output &&
+	test_cmp expect output
+'
+
 test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 10/11] commit --amend: copy notes to the new commit
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (8 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 09/11] rebase: support automatic notes copying Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:10       ` [PATCH v4 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
  2010-02-22  0:25       ` [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Unchanged from v3

 builtin-commit.c  |    6 ++++++
 t/t7501-commit.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index f476d85..ccc4f92 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -1340,6 +1340,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
+		struct notes_rewrite_cfg *cfg;
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(head_sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..8297cb4 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v4 11/11] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (9 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 10/11] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-02-22  0:10       ` Thomas Rast
  2010-02-22  0:25       ` [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
  11 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:10 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
hence determines the notes tree that will be manipulated.  It also
DWIMs a refs/notes/ prefix.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changed since v3: refs/notes/ is now implied, instead of erroring out
if it is not there.

 Documentation/git-notes.txt |    4 ++++
 builtin-notes.c             |   16 ++++++++++++++++
 2 files changed, 20 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 92f1249..dcdcc95 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -115,6 +115,10 @@ OPTIONS
 	Like '-C', but with '-c' the editor is invoked, so that
 	the user can further edit the note message.
 
+--ref <ref>::
+	Manipulate the notes tree in <ref>.  This overrides both
+	GIT_NOTES_REF and the "core.notesRef" configuration.
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/builtin-notes.c b/builtin-notes.c
index 8c65709..0300efa 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -447,6 +447,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	const char *rewrite_cmd = NULL;
+	const char *override_notes_ref = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -459,6 +460,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
 		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
@@ -468,6 +471,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 
+	if (override_notes_ref) {
+		struct strbuf sb = STRBUF_INIT;
+		if (!prefixcmp(override_notes_ref, "refs/notes/"))
+			/* we're happy */;
+		else if (!prefixcmp(override_notes_ref, "notes/"))
+			strbuf_addstr(&sb, "refs/");
+		else
+			strbuf_addstr(&sb, "refs/notes/");
+		strbuf_addstr(&sb, override_notes_ref);
+		setenv("GIT_NOTES_REF", sb.buf, 1);
+		strbuf_release(&sb);
+	}
+
 	if (argc && !strcmp(argv[0], "list"))
 		list = 1;
 	else if (argc && !strcmp(argv[0], "add"))
-- 
1.7.0.147.g5aeb9

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting
  2010-02-21  6:14       ` Thomas Rast
@ 2010-02-22  0:18         ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-22  0:18 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> On Sunday 21 February 2010 04:47:37 Junio C Hamano wrote:
>> 
>> I like the general direction in which this series is going, but a config
>> that does not have any command line counterpart is a design mistake.
>
> I hate[1] to point out that according to the same criterion, the
> *current* notes are a design mistake.  Unless you count GIT_NOTES_REF,
> in which case in my opinion...

With "--no-notes" the caller can explicitly say "please do not show any
notes at all" to the log family.  "git notes" command itself is about
manipluating the notes, and at least with GIT_NOTES_REF the caller can
explicitly say what notes tree to work with even when configured value
is not appropriate.  I also thought there was a work in progress to allow
overriding GIT_NOTES_REF from the command line?

Having _only_ configuration option is practically no better than having
only hardcoded defaults with _no_ way to override it per invocation.  What
if a user wants to usually see notes from set A (hence it is configured as
the value to display-notes-ref configuration) but wants to see notes from
a different (maybe overlapping, maybe not) set B during another task?  As
far as I can see, the _only_ way the current round of RFC lets me do this
is by resetting the configuration in the repository.

It is not a contrived example.  I've been adding the info about the
original mailing list message with a patch to commits made with "am" in
notes/amlog, and would want to keep test result summary in notes/test.
I'd want to see the test notes just before merging topics to integration
branches as a final safety check but not all the time.  I'd want to see
amlog notes when bisect points at a commit in not so near future to be
able to dig up the archive with, but not all the time.  There needs a
per-invocation way to specify which sets of notes to show, and a command
line option is the usual way for commands to allow users to do so [*1*].

> I don't really want to go the other way and patch every command that
> could conceivably have notes in its output.

It is Ok that the series may not be perfect during the RFC period (which
the patches were clearly marked as), but I've already said "I like the
general direction", wanted to see this series advance from RFC to 'next'
material sooner rather than later, and was pointing out what needs to
happen for that.


[Footnote]

*1* We could also use an environment variable, but that is an inferiour
alternative to options and configuration variables from the point of view
of UI design.  Users can export GIT_AUTHOR_NAME, which can serve as an
alternative to user.name.  This can be overridden per-invocation by
"GIT_AUTHOR_NAME=myothername git commit", which can serve as an
alternative to --author option.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
                         ` (10 preceding siblings ...)
  2010-02-22  0:10       ` [PATCH v4 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-02-22  0:25       ` Junio C Hamano
  2010-02-22  0:32         ` Thomas Rast
  11 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-22  0:25 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> core.notesRef should be in that list
> implicitly, and I think the natural position if it is unsorted would
> be: first.

I think that is a sane thing to do, and then any additional ones can come
after that first one, in the order specified by displayRef.  Isn't that
what you are doing, no?

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-22  0:25       ` [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
@ 2010-02-22  0:32         ` Thomas Rast
  0 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-22  0:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

On Monday 22 February 2010 01:25:02 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> 
> > core.notesRef should be in that list
> > implicitly, and I think the natural position if it is unsorted would
> > be: first.
> 
> I think that is a sane thing to do, and then any additional ones can come
> after that first one, in the order specified by displayRef.  Isn't that
> what you are doing, no?

Yes, I just wanted to spell out why I originally decided for the
sorted instead of user-ordered way:

> > However, that means that changing core.notesRef or GIT_NOTES_REF
> > can shuffle around your notes, which I thought would be confusing.

But that wasn't a strong preference at all, so doing it like you
suggested (and like the code is in v4) is fine by me.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v4 02/11] Support showing notes from more than one notes tree
  2010-02-22  0:10       ` [PATCH v4 02/11] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-22 23:20         ` Junio C Hamano
  2010-02-22 23:25           ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-22 23:20 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 4c36aa9..a0f2b0f 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -517,7 +517,7 @@ core.notesRef::
>  	after the full SHA-1 of the commit they annotate.
>  +
>  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
> +appended to the commit message, separated by a "Notes from <refname>:" line.  If the

Hmm...

> diff --git a/notes.c b/notes.c
> index 3ba3e6d..c480370 100644
> --- a/notes.c
> +++ b/notes.c
> @@ -68,6 +70,9 @@ struct non_note {
>  
>  struct notes_tree default_notes_tree;
>  
> +struct string_list display_notes_refs;
> +struct notes_tree **display_notes_trees;

Do these need to be extern?

> +void string_list_add_refs_from_glob_list(struct string_list *list,
> +					 const char *globs)
> +{
> +	struct strbuf globbuf = STRBUF_INIT;
> +	struct strbuf **split;
> +	int i;
> +
> +	strbuf_addstr(&globbuf, globs);
> +	split = strbuf_split(&globbuf, ':');
> +
> +	for (i = 0; split[i]; i++) {
> +		if (!split[i]->len)
> +			continue;
> +		if (split[i]->buf[split[i]->len-1] == ':')
> +			strbuf_setlen(split[i], split[i]->len-1);
> +		string_list_add_refs_by_glob(list, split[i]->buf);
> +	}

Nice use of strbuf_split().  I wonder if we should automatically add
"refs/" and/or "refs/notes/" when the input is missing the prefix.  I
don't have strong preference myself but the users might make noises.

Either way it needs to be documented in the final version before the
series goes to 'master'.

> +static int notes_display_config(const char *k, const char *v, void *cb)
> +{
> +	/* Warning!  This is currently not executed if
> +	 * GIT_NOTES_DISPLAY_REF is set.  Move the git_config() call
> +	 * outside the test if you add more options. */

Yuck.  If you know what needs to be done, do that before other poeple add
more options, please.

> @@ -1016,7 +1135,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
>  	if (msglen && msg[msglen - 1] == '\n')
>  		msglen--;
>  
> -	if (flags & NOTES_SHOW_HEADER)
> +	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
> +		const char *ref = t->ref;
> +		if (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
> +			strbuf_addstr(sb, "\nNotes:\n");
> +		} else {
> +			if (!prefixcmp(ref, "refs/"))
> +				ref += 5;
> +			if (!prefixcmp(ref, "notes/"))
> +				ref += 6;
> +			strbuf_addf(sb, "\nNotes (%s):\n", ref);
> +		}
> +	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
>  		strbuf_addstr(sb, "\nNotes:\n");


It is not clear what the distinction between NOTES_SHOW_HEADER and
NOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with
NOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?

I expected to see "Notes:\n" regardless of the mode if the notes is coming
from the default refs/notes/commits tree, but it probably is better to say
"Notes (commits):\n" like your patch does.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v4 02/11] Support showing notes from more than one notes tree
  2010-02-22 23:20         ` Junio C Hamano
@ 2010-02-22 23:25           ` Thomas Rast
  2010-02-23  0:21             ` Junio C Hamano
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-22 23:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

Umm, since I'm currently working on another reroll that has pretty
args to add notes refs for display...

On Tuesday 23 February 2010 00:20:06 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> > +	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
> > +		const char *ref = t->ref;
> > +		if (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
> > +			strbuf_addstr(sb, "\nNotes:\n");
> > +		} else {
> > +			if (!prefixcmp(ref, "refs/"))
> > +				ref += 5;
> > +			if (!prefixcmp(ref, "notes/"))
> > +				ref += 6;
> > +			strbuf_addf(sb, "\nNotes (%s):\n", ref);
> > +		}
> > +	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
> >  		strbuf_addstr(sb, "\nNotes:\n");
> 
> It is not clear what the distinction between NOTES_SHOW_HEADER and
> NOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with
> NOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?

No.

> I expected to see "Notes:\n" regardless of the mode if the notes is coming
> from the default refs/notes/commits tree, but it probably is better to say
> "Notes (commits):\n" like your patch does.

I special-cased GIT_NOTES_DEFAULT_REF (which is "refs/notes/commits")
above *at your request* to not change the output in the default case.
So which way do you want it?

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v4 02/11] Support showing notes from more than one notes tree
  2010-02-22 23:25           ` Thomas Rast
@ 2010-02-23  0:21             ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-23  0:21 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Umm, since I'm currently working on another reroll that has pretty
> args to add notes refs for display...
>
> On Tuesday 23 February 2010 00:20:06 Junio C Hamano wrote:
>> Thomas Rast <trast@student.ethz.ch> writes:
>> > +	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
>> > +		const char *ref = t->ref;
>> > +		if (!strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
>> > +			strbuf_addstr(sb, "\nNotes:\n");
>> > +		} else {
>> > +			if (!prefixcmp(ref, "refs/"))
>> > +				ref += 5;
>> > +			if (!prefixcmp(ref, "notes/"))
>> > +				ref += 6;
>> > +			strbuf_addf(sb, "\nNotes (%s):\n", ref);
>> > +		}
>> > +	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
>> >  		strbuf_addstr(sb, "\nNotes:\n");
>> 
>> It is not clear what the distinction between NOTES_SHOW_HEADER and
>> NOTES_SHOW_HEADER_WITH_REF.  Does anybody still call this function with
>> NOTES_SHOW_HEADER alone without NOTES_SHOW_HEADER_WITH_REF?
>
> No.

Meaning nobody will go through the latter "Notes:\n" codepath?  Then what
is that else clause for?

Perhaps I am not reading your code right in which case this part needs a
bit more commenting?

>> I expected to see "Notes:\n" regardless of the mode if the notes is coming
>> from the default refs/notes/commits tree, but it probably is better to say
>> "Notes (commits):\n" like your patch does.
>
> I special-cased GIT_NOTES_DEFAULT_REF (which is "refs/notes/commits")
> above *at your request* to not change the output in the default case.

> So which way do you want it?

I don't have strong preference anymore with the above code.

If some caller called the latter "always show Notes:" codepath, i.e.
SHOW_HEADER is given but without HEADER_WITH_REF, then my preference would
have been more clear.  A caller that gives HEADER_WITH_REF clearly wants
to show more than one notes (perhaps the user used displayref mechanism)
and it would be better to always say which one in order to disambiguate,
and the special case would not be a good idea.  A caller would not give
HEADER_WITH_REF when the user has refs/notes/commits hierarchy and nothing
else, and the user didn't ask for multiple notes trees shown; in such a
case we should show the good-old "Notes:".

But if everybody calls with HEADER_WITH_REF, no matter what the end user
preference is, then it becomes unclear what the right answer would be.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
                       ` (13 preceding siblings ...)
  2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
@ 2010-02-23  0:42     ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
                         ` (12 more replies)
  14 siblings, 13 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

On Tuesday 23 February 2010 00:20:06 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> > diff --git a/notes.c b/notes.c
[...]
> > +struct string_list display_notes_refs;
> > +struct notes_tree **display_notes_trees;
> 
> Do these need to be extern?

Don't think so?  After all it's in the .c and just another static
variable.

> Nice use of strbuf_split().  I wonder if we should automatically add
> "refs/" and/or "refs/notes/" when the input is missing the prefix.  I
> don't have strong preference myself but the users might make noises.
> 
> Either way it needs to be documented in the final version before the
> series goes to 'master'.

I decided to do such dwimmery only on the command line, but not for
the config.  This is consistent with what's currently happening for
core.notesRef.  I also documented this.

> > +static int notes_display_config(const char *k, const char *v, void *cb)
> > +{
> > +	/* Warning!  This is currently not executed if
> > +	 * GIT_NOTES_DISPLAY_REF is set.  Move the git_config() call
> > +	 * outside the test if you add more options. */
> 
> Yuck.  If you know what needs to be done, do that before other poeple add
> more options, please.

*shrug*

I was trying to be smart and save a call to git_config() when we know
we don't care about the config anyway.  After all it does read a bunch
of files.

> >> > +	if (flags & NOTES_SHOW_HEADER_WITH_REF && t->ref) {
[...]
> >> > +	} else if (flags & (NOTES_SHOW_HEADER|NOTES_SHOW_HEADER_WITH_REF))
> >> >  		strbuf_addstr(sb, "\nNotes:\n");
> 
> Meaning nobody will go through the latter "Notes:\n" codepath?  Then what
> is that else clause for?
> 
> Perhaps I am not reading your code right in which case this part needs a
> bit more commenting?

The catch is in the '&& t->ref'.  Again I'm just trying to be a bit
defensive.  Anyway, I ripped out the old formatting and put the NULL
test first this time.

> >> I expected to see "Notes:\n" regardless of the mode if the notes is coming
> >> from the default refs/notes/commits tree, but it probably is better to say
> >> "Notes (commits):\n" like your patch does.
> >
> > I special-cased GIT_NOTES_DEFAULT_REF (which is "refs/notes/commits")
> > above *at your request* to not change the output in the default case.
> 
> > So which way do you want it?
> 
> I don't have strong preference anymore with the above code.
[...]
> But if everybody calls with HEADER_WITH_REF, no matter what the end user
> preference is, then it becomes unclear what the right answer would be.

Well, I think we're into bikeshedding territory now, so I just left it
as it was.


The rest of the changes are listed in each email.


Thomas Rast (11):
  test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  Support showing notes from more than one notes tree
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  notes: add shorthand --ref to override GIT_NOTES_REF

 Documentation/config.txt         |   53 +++++-
 Documentation/git-notes.txt      |   21 ++-
 Documentation/githooks.txt       |   38 ++++
 Documentation/pretty-options.txt |   11 +-
 builtin-commit.c                 |   45 +++++
 builtin-log.c                    |    2 +
 builtin-notes.c                  |  195 +++++++++++++++++++-
 builtin.h                        |   18 ++
 cache.h                          |    3 +
 git-am.sh                        |   13 ++
 git-rebase--interactive.sh       |   52 +++++-
 git-rebase.sh                    |    6 +
 notes.c                          |  191 +++++++++++++++++++-
 notes.h                          |   27 +++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 ++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  377 +++++++++++++++++++++++++++++++++++++-
 t/t3400-rebase.sh                |   17 ++
 t/t3404-rebase-interactive.sh    |   24 +++
 t/t5407-post-rewrite-hook.sh     |  172 +++++++++++++++++
 t/t7501-commit.sh                |   12 ++
 t/test-lib.sh                    |    4 +
 25 files changed, 1294 insertions(+), 28 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v5 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 02/11] Support showing notes from more than one notes tree Thomas Rast
                         ` (11 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

---


 t/test-lib.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index afd3053..1e7eab1 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY
 unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 02/11] Support showing notes from more than one notes tree
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  1:47         ` Junio C Hamano
  2010-02-23  0:42       ` [PATCH v5 03/11] Documentation: document post-rewrite hook Thomas Rast
                         ` (10 subsequent siblings)
  12 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.

Thanks to Junio C Hamano for lots of feedback, which greatly
influenced the design of the entire series and this commit in
particular.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v4:
* introduce --show-notes=<ref> and --[no-]standard-notes
* remove NOTES_SHOW_HEADER_WITH_REF distinction
* parse config even if we don't care about it
* fix misallocation in load_notes_trees()
* doc tweaks

 Documentation/config.txt         |   23 +++++-
 Documentation/pretty-options.txt |   11 ++-
 builtin-log.c                    |    2 +
 cache.h                          |    1 +
 notes.c                          |  171 ++++++++++++++++++++++++++++++++++++--
 notes.h                          |   18 ++++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 +++++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  148 +++++++++++++++++++++++++++++++--
 t/test-lib.sh                    |    1 +
 13 files changed, 392 insertions(+), 24 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 664de6b..e077445 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -519,10 +519,12 @@ check that makes sure that existing object files will not get overwritten.
 core.notesRef::
 	When showing commit messages, also show notes which are stored in
 	the given ref.  This ref is expected to contain files named
-	after the full SHA-1 of the commit they annotate.
+	after the full SHA-1 of the commit they annotate.  The ref
+	must be fully qualified.
 +
 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
+appended to the commit message, separated by a "Notes (<refname>):"
+line (shortened to "Notes:" in the case of "refs/notes/commits").  If the
 given ref itself does not exist, it is not an error, but means that no
 notes should be printed.
 +
@@ -1305,6 +1307,23 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+	The (fully qualified) refname from which to show notes when
+	showing commit messages.  The value of this variable can be set
+	to a glob, in which case notes from all matching refs will be
+	shown.  You may also specify this configuration variable
+	several times.  A warning will be issued for refs that do not
+	exist, but a glob that does not match any refs is silently
+	ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index aa96cae..1d56926 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -30,9 +30,18 @@ people using 80-column terminals.
 	defaults to UTF-8.
 
 --no-notes::
---show-notes::
+--show-notes[=<ref>]::
 	Show the notes (see linkgit:git-notes[1]) that annotate the
 	commit, when showing the commit log message.  This is the default
 	for `git log`, `git show` and `git whatchanged` commands when
 	there is no `--pretty`, `--format` nor `--oneline` option is
 	given on the command line.
++
+With an optional argument, add this ref to the list of notes.  The ref
+is taken to be in `refs/notes/` if it is not qualified.
+
+--[no-]standard-notes::
+	Enable or disable populating the notes ref list from the
+	'core.notesRef' and 'notes.displayRef' variables (or
+	corresponding environment overrides).  See
+	linkgit:git-config[1].
diff --git a/builtin-log.c b/builtin-log.c
index e0d5caa..e7ea088 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -60,6 +60,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 
 	if (!rev->show_notes_given && !rev->pretty_given)
 		rev->show_notes = 1;
+	if (rev->show_notes)
+		init_display_notes(&rev->notes_opt);
 
 	if (rev->diffopt.pickaxe || rev->diffopt.filter)
 		rev->always_show_header = 0;
diff --git a/cache.h b/cache.h
index c863085..ee4ae3e 100644
--- a/cache.h
+++ b/cache.h
@@ -387,6 +387,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
diff --git a/notes.c b/notes.c
index 3ba3e6d..ee54a42 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+struct string_list display_notes_refs;
+struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,76 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+				   int flag, void *cb)
+{
+	struct string_list *refs = cb;
+	if (!unsorted_string_list_has_string(refs, path))
+		string_list_append(path, refs);
+	return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+	if (has_glob_specials(glob)) {
+		for_each_glob_ref(string_list_add_one_ref, glob, list);
+	} else {
+		unsigned char sha1[20];
+		if (get_sha1(glob, sha1))
+			warning("notes ref %s is invalid", glob);
+		if (!unsorted_string_list_has_string(list, glob))
+			string_list_append(glob, list);
+	}
+}
+
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs)
+{
+	struct strbuf globbuf = STRBUF_INIT;
+	struct strbuf **split;
+	int i;
+
+	strbuf_addstr(&globbuf, globs);
+	split = strbuf_split(&globbuf, ':');
+
+	for (i = 0; split[i]; i++) {
+		if (!split[i]->len)
+			continue;
+		if (split[i]->buf[split[i]->len-1] == ':')
+			strbuf_setlen(split[i], split[i]->len-1);
+		string_list_add_refs_by_glob(list, split[i]->buf);
+	}
+
+	strbuf_list_free(split);
+	strbuf_release(&globbuf);
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	int *load_refs = cb;
+
+	if (*load_refs && !strcmp(k, "notes.displayref")) {
+		if (!v)
+			config_error_nonbool(k);
+		string_list_add_refs_by_glob(&display_notes_refs, v);
+		return 0;
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref()
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +915,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +939,71 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+struct load_notes_cb_data
+{
+	int counter;
+	struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	struct load_notes_cb_data *c = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	c->trees[c->counter++] = t;
+	return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+	struct notes_tree **trees;
+	struct load_notes_cb_data cb_data;
+	trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
+	cb_data.counter = 0;
+	cb_data.trees = trees;
+	for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+	trees[cb_data.counter] = NULL;
+	return trees;
+}
+
+static int string_list_add_refs_from_list(struct string_list_item *item,
+					  void *cb)
+{
+	struct string_list *list = cb;
+	string_list_add_refs_by_glob(list, item->string);
+	return 0;
+}
+
+void init_display_notes(struct display_notes_opt *opt)
+{
+	char *display_ref_env;
+	int load_config_refs = 0;
+	display_notes_refs.strdup_strings = 1;
+
+	if (!opt || !opt->suppress_default_notes) {
+		string_list_append(default_notes_ref(), &display_notes_refs);
+		display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+		if (display_ref_env) {
+			string_list_add_refs_from_colon_sep(&display_notes_refs,
+							    display_ref_env);
+			load_config_refs = 0;
+		} else {
+			load_config_refs = 1;
+		}
+	}
+
+	git_config(notes_display_config, &load_config_refs);
+
+	if (opt && opt->extra_notes_refs)
+		for_each_string_list(string_list_add_refs_from_list,
+				     opt->extra_notes_refs,
+				     &display_notes_refs);
+
+	display_notes_trees = load_notes_trees(&display_notes_refs);
+	string_list_clear(&display_notes_refs, 0);
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,8 +1152,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
-		strbuf_addstr(sb, "\nNotes:\n");
+	if (flags & NOTES_SHOW_HEADER) {
+		const char *ref = t->ref;
+		if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+			strbuf_addstr(sb, "\nNotes:\n");
+		} else {
+			if (!prefixcmp(ref, "refs/"))
+				ref += 5;
+			if (!prefixcmp(ref, "notes/"))
+				ref += 6;
+			strbuf_addf(sb, "\nNotes (%s):\n", ref);
+		}
+	}
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
 		linelen = strchrnul(msg_p, '\n') - msg_p;
@@ -1030,3 +1176,14 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	if (!display_notes_trees)
+		init_display_notes(NULL);
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
diff --git a/notes.h b/notes.h
index bad03cc..7650254 100644
--- a/notes.h
+++ b/notes.h
@@ -198,4 +198,22 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 		struct strbuf *sb, const char *output_encoding, int flags);
 
+
+struct string_list;
+
+struct display_notes_opt
+{
+	int suppress_default_notes : 1;
+	struct string_list *extra_notes_refs;
+};
+
+void init_display_notes(struct display_notes_opt *opt);
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags);
+
+struct notes_tree **load_notes_trees(struct string_list *refs);
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs);
+
 #endif
diff --git a/pretty.c b/pretty.c
index f999485..c185fd2 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);
 
 	free(reencoded);
 }
diff --git a/refs.c b/refs.c
index f3fcbe0..5a860c4 100644
--- a/refs.c
+++ b/refs.c
@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 {
 	struct strbuf real_pattern = STRBUF_INIT;
 	struct ref_filter filter;
-	const char *has_glob_specials;
 	int ret;
 
 	if (!prefix && prefixcmp(pattern, "refs/"))
@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 		strbuf_addstr(&real_pattern, prefix);
 	strbuf_addstr(&real_pattern, pattern);
 
-	has_glob_specials = strpbrk(pattern, "?*[");
-	if (!has_glob_specials) {
+	if (!has_glob_specials(pattern)) {
 		/* Append implied '/' '*' if not present. */
 		if (real_pattern.buf[real_pattern.len - 1] != '/')
 			strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9..4a18b08 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ struct ref_lock {
 extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
 extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
+static inline const char *has_glob_specials(const char *pattern)
+{
+	return strpbrk(pattern, "?*[");
+}
+
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
diff --git a/revision.c b/revision.c
index 29721ec..d6e842e 100644
--- a/revision.c
+++ b/revision.c
@@ -12,6 +12,7 @@
 #include "patch-ids.h"
 #include "decorate.h"
 #include "log-tree.h"
+#include "string-list.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -1191,9 +1192,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--show-notes")) {
 		revs->show_notes = 1;
 		revs->show_notes_given = 1;
+	} else if (!prefixcmp(arg, "--show-notes=")) {
+		struct strbuf buf = STRBUF_INIT;
+		revs->show_notes = 1;
+		revs->show_notes_given = 1;
+		if (!revs->notes_opt.extra_notes_refs)
+			revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
+		if (!prefixcmp(arg+13, "refs/"))
+			/* happy */;
+		else if (!prefixcmp(arg+13, "notes/"))
+			strbuf_addstr(&buf, "refs/");
+		else
+			strbuf_addstr(&buf, "refs/notes/");
+		strbuf_addstr(&buf, arg+13);
+		string_list_append(strbuf_detach(&buf, NULL),
+				   revs->notes_opt.extra_notes_refs);
 	} else if (!strcmp(arg, "--no-notes")) {
 		revs->show_notes = 0;
 		revs->show_notes_given = 1;
+	} else if (!strcmp(arg, "--standard-notes")) {
+		revs->show_notes_given = 1;
+		revs->notes_opt.suppress_default_notes = 0;
+	} else if (!strcmp(arg, "--no-standard-notes")) {
+		revs->notes_opt.suppress_default_notes = 1;
 	} else if (!strcmp(arg, "--oneline")) {
 		revs->verbose_header = 1;
 		get_commit_format("oneline", revs);
diff --git a/revision.h b/revision.h
index a14deef..580f6ec 100644
--- a/revision.h
+++ b/revision.h
@@ -3,6 +3,7 @@
 
 #include "parse-options.h"
 #include "grep.h"
+#include "notes.h"
 
 #define SEEN		(1u<<0)
 #define UNINTERESTING   (1u<<1)
@@ -20,6 +21,7 @@
 
 struct rev_info;
 struct log_info;
+struct string_list;
 
 struct rev_info {
 	/* Starting list */
@@ -126,6 +128,9 @@ struct rev_info {
 	struct reflog_walk_info *reflog_info;
 	struct decoration children;
 	struct decoration merge_simplification;
+
+	/* notes-specific options: which refs to show */
+	struct display_notes_opt notes_opt;
 };
 
 #define REV_TREE_SAME		0
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 90178f9..cb7166f 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -415,7 +415,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700
 
     6th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -448,7 +448,139 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 	test_cmp expect-not-other output
 '
 
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+
+Notes (other):
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	GIT_NOTES_REF=refs/notes/commits git notes add \
+		-m"replacement for deleted note" HEAD^ &&
+	GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+	git config --unset core.notesRef &&
+	git config notes.displayRef "refs/notes/*" &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+	git config core.notesRef refs/notes/commits &&
+	git config notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+	git config --unset core.notesRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git config --add notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+
+Notes:
+    order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+	git config core.notesRef refs/notes/other &&
+	git config --unset-all notes.displayRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git log -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+	git config --unset-all core.notesRef &&
+	git config --unset-all notes.displayRef &&
+	GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+		git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+	git config notes.displayRef "refs/notes/*" &&
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+	test_cmp expect-none output
+'
+
+test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-commits << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+EOF
+
+test_expect_success '--no-standard-notes' '
+	git log --no-standard-notes --show-notes=commits -1 > output &&
+	test_cmp expect-commits output
+'
+
+test_expect_success '--standard-notes' '
+	git log --no-standard-notes --show-notes=commits \
+		--standard-notes -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success '--show-notes=ref accumulates' '
+	git log --show-notes=other --show-notes=commits \
+		 --no-standard-notes -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+	git config core.notesRef refs/notes/other &&
 	echo "Note on a tree" > expect
 	git notes add -m "Note on a tree" HEAD: &&
 	git notes show HEAD: > actual &&
@@ -472,7 +604,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700
 
     7th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -503,7 +635,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 EOF
 
@@ -533,7 +665,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
@@ -552,7 +684,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -569,7 +701,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -588,7 +720,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -619,7 +751,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 1e7eab1..c814e9c 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -55,6 +55,7 @@ unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 03/11] Documentation: document post-rewrite hook
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 02/11] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 04/11] commit --amend: invoke " Thomas Rast
                         ` (9 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

* adapt docs to the fact that git-filter-branch doesn't call the hook
  in this series
* specify commit ordering for rebase

 Documentation/githooks.txt |   34 ++++++++++++++++++++++++++++++++++
 1 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..a741769 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,40 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+it!).  Its first argument denotes the command it was invoked by:
+currently one of `amend` or `rebase`.  Further command-dependent
+arguments may be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
++
+The commits are guaranteed to be listed in the order that they were
+processed by rebase.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 04/11] commit --amend: invoke post-rewrite hook
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (2 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 03/11] Documentation: document post-rewrite hook Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 05/11] rebase: " Thomas Rast
                         ` (8 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin-commit.c             |   39 +++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin-commit.c b/builtin-commit.c
index bb97000..1c3aa7d 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		run_rewrite_hook(head_sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 3e4fd14..5735859 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -445,7 +445,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 05/11] rebase: invoke post-rewrite hook
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (3 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 04/11] commit --amend: invoke " Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 06/11] rebase -i: " Thomas Rast
                         ` (7 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index b11af03..5a20a05 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -575,6 +575,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -582,6 +583,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -765,6 +767,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -773,6 +779,10 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 git gc --auto
 
 rm -fr "$dotest"
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 06/11] rebase -i: invoke post-rewrite hook
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (4 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 05/11] rebase: " Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-24  6:15         ` Junio C Hamano
  2010-02-23  0:42       ` [PATCH v5 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
                         ` (6 subsequent siblings)
  12 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-rebase--interactive.sh   |   46 +++++++++++++++++++++-
 t/t5407-post-rewrite-hook.sh |   90 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+), 1 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 5735859..564f6ac 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/stopped-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -446,6 +476,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -453,6 +484,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		echo "$1" > "$DOTEST"/stopped-sha
 		make_patch $sha1
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
@@ -509,6 +541,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -571,7 +609,12 @@ skip_unnecessary_picks () {
 		esac
 		echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
 	done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
-	mv -f "$TODO".new "$TODO" ||
+	mv -f "$TODO".new "$TODO" &&
+	case "$(peek_next_command)" in
+	squash|s|fixup|f)
+		record_in_rewritten "$ONTO"
+		;;
+	esac ||
 	die "Could not skip unnecessary pick commands"
 }
 
@@ -685,6 +728,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..e1fccc4 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,94 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 fixup 2" git rebase -i B &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 07/11] notes: implement 'git notes copy --stdin'
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (5 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 06/11] rebase -i: " Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
                         ` (5 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/git-notes.txt |   12 ++++++++-
 builtin-notes.c             |   56 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   20 +++++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   34 ++++++++++++++++++++++++++
 5 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 14f73b9..f67cb6a 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -55,6 +55,16 @@ copy::
 	objects has none. (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin-notes.c b/builtin-notes.c
index 123ecad..daeb14e 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -278,6 +278,46 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	free_notes(t);
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -287,7 +327,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -301,6 +341,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT",
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -349,8 +390,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				return notes_copy_from_stdin(force);
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index ee54a42..9246358 100644
--- a/notes.c
+++ b/notes.c
@@ -1187,3 +1187,23 @@ void format_display_notes(const unsigned char *object_sha1,
 		format_note(display_notes_trees[i], object_sha1, sb,
 			    output_encoding, flags);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note = get_note(t, from_obj);
+	const unsigned char *existing_note = get_note(t, to_obj);
+
+	if (!force && existing_note)
+		return 1;
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else if (existing_note) {
+		add_note(t, to_obj, null_sha1, combine_fn);
+	} else
+		remove_note(t, to_obj);
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index 7650254..ec613ca 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index cb7166f..60ad6a1 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -776,4 +776,38 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	test_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 08/11] notes: implement helpers needed for note copying during rewrite
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (6 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 09/11] rebase: support automatic notes copying Thomas Rast
                         ` (4 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v4:
* doc tweaks
* enforce full qualification of refs

 Documentation/config.txt    |   30 +++++++
 Documentation/git-notes.txt |    4 +
 Documentation/githooks.txt  |    4 +
 builtin-notes.c             |  139 +++++++++++++++++++++++++++++--
 builtin.h                   |   18 ++++
 cache.h                     |    2 +
 t/t3301-notes.sh            |  195 +++++++++++++++++++++++++++++++++++++++++++
 t/test-lib.sh               |    2 +
 8 files changed, 386 insertions(+), 8 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index e077445..dffe1f6 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1324,6 +1324,36 @@ The effective value of "core.notesRef" (possibly overridden by
 GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `true`, but see
+	"notes.rewriteRef" below.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+	When copying notes during a rewrite, specifies the (fully
+	qualified) ref whose notes should be copied.  The ref may be a
+	glob, in which case notes in all matching refs will be copied.
+	You may also specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index f67cb6a..92f1249 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -34,6 +34,10 @@ 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".
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index a741769..7183aa9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin-notes.c b/builtin-notes.c
index daeb14e..026cfd3 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -16,6 +16,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static const char * const git_notes_usage[] = {
 	"git notes [list [<object>]]",
@@ -278,14 +279,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+		if (!v)
+			config_error_nonbool(k);
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	} else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+		/* note that a refs/ prefix is implied in the
+		 * underlying for_each_glob_ref */
+		if (!prefixcmp(v, "refs/notes/"))
+			string_list_add_refs_by_glob(c->refs, v);
+		else
+			warning("Refusing to rewrite notes in %s"
+				" (outside of refs/notes/)", v);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+	const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+	c->cmd = cmd;
+	c->enabled = 1;
+	c->combine = combine_notes_concatenate;
+	c->refs = xcalloc(1, sizeof(struct string_list));
+	c->refs->strdup_strings = 1;
+	c->refs_from_env = 0;
+	c->mode_from_env = 0;
+	if (rewrite_mode_env) {
+		c->mode_from_env = 1;
+		c->combine = parse_combine_notes_fn(rewrite_mode_env);
+		if (!c->combine)
+			error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
+			      " value: '%s'", rewrite_mode_env);
+	}
+	if (rewrite_refs_env) {
+		c->refs_from_env = 1;
+		string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+	}
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled || !c->refs->nr) {
+		string_list_clear(c->refs, 0);
+		free(c->refs);
+		free(c);
+		return NULL;
+	}
+	c->trees = load_notes_trees(c->refs);
+	string_list_clear(c->refs, 0);
+	free(c->refs);
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	int ret = 0;
+	int i;
+	for (i = 0; c->trees[i]; i++)
+		ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+	return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	int i;
+	for (i = 0; c->trees[i]; i++) {
+		commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+		free_notes(c->trees[i]);
+	}
+	free(c->trees);
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
+	struct notes_rewrite_cfg *c = NULL;
 	struct notes_tree *t;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -302,7 +410,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -313,8 +425,12 @@ int notes_copy_from_stdin(int force)
 		strbuf_list_free(split);
 	}
 
-	commit_notes(t, "Notes added by 'git notes copy'");
-	free_notes(t);
+	if (!rewrite_cmd) {
+		commit_notes(t, "Notes added by 'git notes copy'");
+		free_notes(t);
+	} else {
+		finish_copy_notes_for_rewrite(c);
+	}
 	return ret;
 }
 
@@ -330,6 +446,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -342,6 +459,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -390,6 +509,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -397,12 +520,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				return notes_copy_from_stdin(force);
+				return notes_copy_from_stdin(force, rewrite_cmd);
 			}
 		}
 		if (argc < 3) {
diff --git a/builtin.h b/builtin.h
index cdf9847..8aebe61 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,24 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree **trees;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+	struct string_list *refs;
+	int refs_from_env;
+	int mode_from_env;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index ee4ae3e..39450bc 100644
--- a/cache.h
+++ b/cache.h
@@ -388,6 +388,8 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 60ad6a1..aeec90a 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -810,4 +810,199 @@ test_expect_success 'git notes copy --stdin' '
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewriteMode overwrite &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+	git config notes.rewrite.bar false &&
+	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=bar &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+    append 1
+    append 2
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+	git notes add -f -m"append 1" HEAD^ &&
+	git notes add -f -m"append 2" HEAD^^ &&
+	(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
+	echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
+	git notes remove HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 1
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+	git notes add -f -m"replacement note 1" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 2
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+	git config notes.rewriteMode overwrite &&
+	git notes add -f -m"replacement note 2" HEAD^ &&
+	git config --unset-all notes.rewriteRef &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
+		git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
+	git config notes.rewriteRef refs/notes/other &&
+	git notes add -f -m"replacement note 3" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index c814e9c..4caad9f 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
 unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 09/11] rebase: support automatic notes copying
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (7 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-25  3:58         ` Junio C Hamano
  2010-02-23  0:42       ` [PATCH v5 10/11] commit --amend: copy notes to the new commit Thomas Rast
                         ` (3 subsequent siblings)
  12 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   17 +++++++++++++++++
 t/t3404-rebase-interactive.sh |   24 ++++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 5a20a05..a0ed1a6 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -779,8 +779,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 git gc --auto
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 564f6ac..415ae72 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -570,6 +570,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..dbf7dfb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..19668c2 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,28 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+	git reset --hard n3 &&
+	git notes add -m"an earlier note" n2 &&
+	GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+	git notes show > output &&
+	test_cmp expect output
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 10/11] commit --amend: copy notes to the new commit
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (8 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 09/11] rebase: support automatic notes copying Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:42       ` [PATCH v5 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
                         ` (2 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin-commit.c  |    6 ++++++
 t/t7501-commit.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 1c3aa7d..3f1708b 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -1340,6 +1340,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
+		struct notes_rewrite_cfg *cfg;
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(head_sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..8297cb4 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v5 11/11] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (9 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 10/11] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-02-23  0:42       ` Thomas Rast
  2010-02-23  0:49       ` [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
  2010-02-23  0:49       ` Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:42 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
hence determines the notes tree that will be manipulated.  It also
DWIMs a refs/notes/ prefix.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v4:
* doc tweaks

 Documentation/git-notes.txt |    5 +++++
 builtin-notes.c             |   16 ++++++++++++++++
 2 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 92f1249..467a963 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -115,6 +115,11 @@ OPTIONS
 	Like '-C', but with '-c' the editor is invoked, so that
 	the user can further edit the note message.
 
+--ref <ref>::
+	Manipulate the notes tree in <ref>.  This overrides both
+	GIT_NOTES_REF and the "core.notesRef" configuration.  The ref
+	is taken to be in `refs/notes/` if it is not qualified.
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/builtin-notes.c b/builtin-notes.c
index 026cfd3..2e45be9 100644
--- a/builtin-notes.c
+++ b/builtin-notes.c
@@ -447,6 +447,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	const char *rewrite_cmd = NULL;
+	const char *override_notes_ref = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes options"),
 		OPT_CALLBACK('m', "message", &msg, "MSG",
@@ -459,6 +460,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			   "reuse specified note object", parse_reuse_arg),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
 		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
@@ -468,6 +471,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 
+	if (override_notes_ref) {
+		struct strbuf sb = STRBUF_INIT;
+		if (!prefixcmp(override_notes_ref, "refs/notes/"))
+			/* we're happy */;
+		else if (!prefixcmp(override_notes_ref, "notes/"))
+			strbuf_addstr(&sb, "refs/");
+		else
+			strbuf_addstr(&sb, "refs/notes/");
+		strbuf_addstr(&sb, override_notes_ref);
+		setenv("GIT_NOTES_REF", sb.buf, 1);
+		strbuf_release(&sb);
+	}
+
 	if (argc && !strcmp(argv[0], "list"))
 		list = 1;
 	else if (argc && !strcmp(argv[0], "add"))
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (10 preceding siblings ...)
  2010-02-23  0:42       ` [PATCH v5 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-02-23  0:49       ` Junio C Hamano
  2010-02-23  0:49       ` Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-23  0:49 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

>> Yuck.  If you know what needs to be done, do that before other poeple add
>> more options, please.
>
> *shrug*
>
> I was trying to be smart and save a call to git_config() when we know
> we don't care about the config anyway.  After all it does read a bunch
> of files.

You can set a flag to skip reading the trees (which is the expensive part)
you know you are not going to use to optimize out the expensive part of
the code, no?

>> Perhaps I am not reading your code right in which case this part needs a
>> bit more commenting?
>
> The catch is in the '&& t->ref'.

Ahh, Ok, so that was what I didn't see.  Perhaps it needs to say t->ref is
set only under such and such conditions.  I am reasonably sure the next
person who needs to change this part of the code would appreciate such a
comment and that person may even be you 3 months from now.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting
  2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
                         ` (11 preceding siblings ...)
  2010-02-23  0:49       ` [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
@ 2010-02-23  0:49       ` Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23  0:49 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Please ignore until I post a replacement for 2/11.  I managed to
mis-edit *and* forget to test during the last round of fixups, and now
it doesn't even compile.  I'll just go to bed and try again tomorrow.

Sorry.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 02/11] Support showing notes from more than one notes tree
  2010-02-23  0:42       ` [PATCH v5 02/11] Support showing notes from more than one notes tree Thomas Rast
@ 2010-02-23  1:47         ` Junio C Hamano
  2010-02-23 17:10           ` Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-23  1:47 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Changes since v4:
> * introduce --show-notes=<ref> and --[no-]standard-notes

Nicer, much nicer.

> * remove NOTES_SHOW_HEADER_WITH_REF distinction

Note that there is a leftover caller that uses the symbol without noticing
that it has been retired.  I'll fix it up locally when I queue the series
to 'pu'.

> * parse config even if we don't care about it

Parsing is good, and not reading trees is very good.

It is silly to put yourself down after doing both of these very well by
saying "even if we don't care about it"---you obviously do care about
making it earier for other people to build on top of your code.  Otherwise
you would have left the code unchanged from the previous round ;-).

> diff --git a/notes.c b/notes.c
> index 3ba3e6d..ee54a42 100644
> --- a/notes.c
> +++ b/notes.c
> @@ -5,6 +5,8 @@
>  #include "utf8.h"
>  #include "strbuf.h"
>  #include "tree-walk.h"
> +#include "string-list.h"
> +#include "refs.h"
>  
>  /*
>   * Use a non-balancing simple 16-tree structure with struct int_node as
> @@ -68,6 +70,9 @@ struct non_note {
>  
>  struct notes_tree default_notes_tree;
>  
> +struct string_list display_notes_refs;
> +struct notes_tree **display_notes_trees;

Unlike default_notes_tree, the above two can become static, as you made
the new logic better contained to this file and accessible only via
accessor functions.

> @@ -828,6 +833,76 @@ int combine_notes_ignore(unsigned char *cur_sha1,
> ...
> +static const char *default_notes_ref()

I'll s/_ref()/_ref(void)/ here.

> diff --git a/notes.h b/notes.h
> index bad03cc..7650254 100644
> --- a/notes.h
> +++ b/notes.h
> @@ -198,4 +198,22 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
>  void format_note(struct notes_tree *t, const unsigned char *object_sha1,
>  		struct strbuf *sb, const char *output_encoding, int flags);
>  
> +
> +struct string_list;
> +
> +struct display_notes_opt
> +{
> +	int suppress_default_notes : 1;
> +	struct string_list *extra_notes_refs;
> +};
> +
> +void init_display_notes(struct display_notes_opt *opt);
> +void format_display_notes(const unsigned char *object_sha1,
> +			  struct strbuf *sb, const char *output_encoding, int flags);
> +

As you are retiring format_note() as a public interface, and instead
making format_display_notes() as the primary API for the callers, the
former should be made static to notes.c along with the large comment
describing how to call it.  It may also be worth telling people how to
call this new public interface with similar comment here.

> @@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
>  		strbuf_addch(sb, '\n');
>  
>  	if (context->show_notes)
> -		format_note(NULL, commit->object.sha1, sb, encoding,
> -			    NOTES_SHOW_HEADER | NOTES_INDENT);
> +		format_display_notes(commit->object.sha1, sb, encoding,
> +				     NOTES_SHOW_HEADER_WITH_REF | NOTES_INDENT);

I'll s/_WITH_REF// here.

> diff --git a/revision.c b/revision.c
> index 29721ec..d6e842e 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -1191,9 +1192,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
>  	} else if (!strcmp(arg, "--show-notes")) {
>  		revs->show_notes = 1;
>  		revs->show_notes_given = 1;
> +	} else if (!prefixcmp(arg, "--show-notes=")) {
> +		struct strbuf buf = STRBUF_INIT;
> +		revs->show_notes = 1;
> +		revs->show_notes_given = 1;
> +		if (!revs->notes_opt.extra_notes_refs)
> +			revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
> +		if (!prefixcmp(arg+13, "refs/"))
> +			/* happy */;
> +		else if (!prefixcmp(arg+13, "notes/"))
> +			strbuf_addstr(&buf, "refs/");
> +		else
> +			strbuf_addstr(&buf, "refs/notes/");
> +		strbuf_addstr(&buf, arg+13);
> +		string_list_append(strbuf_detach(&buf, NULL),
> +				   revs->notes_opt.extra_notes_refs);

Nice; multiple --show-notes=... will accumulate in the order given.  I
knew you won't be stupid to make this a colon separated string, but I had
to double check ;-).

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 02/11] Support showing notes from more than one notes tree
  2010-02-23  1:47         ` Junio C Hamano
@ 2010-02-23 17:10           ` Thomas Rast
  2010-02-23 17:34             ` [PATCH] format-patch: learn to fill comment section of email from notes Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23 17:10 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Johannes Sixt, Johan Herland

On Tuesday 23 February 2010 02:47:40 Junio C Hamano wrote:
> Thomas Rast <trast@student.ethz.ch> writes:
> > * remove NOTES_SHOW_HEADER_WITH_REF distinction
> 
> Note that there is a leftover caller that uses the symbol without noticing
> that it has been retired.  I'll fix it up locally when I queue the series
> to 'pu'.

Thanks.  That was the breakage I hinted at in the last mail last
night; since it (hopefully) was the only problem with the series, I'll
dispense with the replacement for now.

> > @@ -68,6 +70,9 @@ struct non_note {
> >  
> >  struct notes_tree default_notes_tree;
> >  
> > +struct string_list display_notes_refs;
> > +struct notes_tree **display_notes_trees;
> 
> Unlike default_notes_tree, the above two can become static, as you made
> the new logic better contained to this file and accessible only via
> accessor functions.

Oh, so that's what you hinted at with the 'extern' question...

> As you are retiring format_note() as a public interface, and instead
> making format_display_notes() as the primary API for the callers, the

I still need it for the "format-patch from notes" patch, to be posted
soonish.  So unless someone comes up with an alternative for that, it
stays a public interface.

> former should be made static to notes.c along with the large comment
> describing how to call it.  It may also be worth telling people how to
> call this new public interface with similar comment here.

You caught me on my comment laziness...  I suspect even the ones I can
add won't match Johan's excellent comments :-)

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH] format-patch: learn to fill comment section of email from notes
  2010-02-23 17:10           ` Thomas Rast
@ 2010-02-23 17:34             ` Thomas Rast
  2010-02-23 17:34               ` [PATCH] BROKEN -- " Thomas Rast
                                 ` (2 more replies)
  0 siblings, 3 replies; 135+ messages in thread
From: Thomas Rast @ 2010-02-23 17:34 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teach git-format-patch a new option --comment-notes (with config
format.commentNotes) which takes a notes ref argument.  These notes
are then added to the patch email between the --- separator and the
diffstat/diff.

This is somewhat tricky because the pretty-printing code has no
control whatsoever over the --- and the diffstat, and there be dragons
in the newline-producing code inside show_log().  Try not to disturb
them, and patch around only outside show_log().

To ensure correctness of the resulting email, extend the existing rule
(separator if not 'oneline' format and --patch-with-stat) to also
include a separator if there are notes and a patch will be included.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

So here's the promised patch.  This is sort-of-RFC; it's not part of
the series because I don't want the latter to block on this one if
anyone suggests changes.

I'll follow up with a broken version to prove the point about the
dragons I made above, if anyone wants to try fixing it.

 Documentation/config.txt           |    6 ++
 Documentation/git-format-patch.txt |   10 ++++
 builtin-log.c                      |   12 +++++
 log-tree.c                         |   14 +++++-
 pretty.c                           |    2 +-
 t/t4014-format-patch.sh            |   91 ++++++++++++++++++++++++++++++++++++
 6 files changed, 133 insertions(+), 2 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index dffe1f6..a9e27db 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -886,6 +886,12 @@ format.signoff::
     the rights to submit this work under the same open source license.
     Please see the 'SubmittingPatches' document for further discussion.
 
+format.commentNotes::
+	The default notes reference that is used for the comment
+	section with --pretty=email and for 'git-format-patch'.  (The
+	comment section is the area between the `\---` separator and
+	the patch, which also holds the diffstat.)
+
 gc.aggressiveWindow::
 	The window size parameter used in the delta compression
 	algorithm used by 'git gc --aggressive'.  This defaults
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 9674f9d..afe7e41 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -198,6 +198,16 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 	range are always formatted as creation patches, independently
 	of this flag.
 
+--comment-notes=<ref>::
+	Use the notes from <ref> to fill the comment section of the
+	email, i.e., the part between the `\---` separator and the
+	patch.  See linkgit:git-notes[1].
++
+Warning: the code currently does not guard against a line in the notes
+that starts with `diff`, which will be treated as the start of the
+patch by 'git-am'.
+
+
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
diff --git a/builtin-log.c b/builtin-log.c
index e7ea088..f85837d 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -19,6 +19,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "notes.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -495,6 +496,7 @@ static void add_header(const char *value)
 #define THREAD_DEEP 2
 static int thread = 0;
 static int do_signoff = 0;
+static const char *comment_notes = NULL;
 
 static int git_format_config(const char *var, const char *value, void *cb)
 {
@@ -548,6 +550,9 @@ static int git_format_config(const char *var, const char *value, void *cb)
 		do_signoff = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "format.commentnotes")) {
+		return git_config_string(&comment_notes, var, value);
+	}
 
 	return git_log_config(var, value, cb);
 }
@@ -955,6 +960,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "thread", &thread, "style",
 			    "enable message threading, styles: shallow, deep",
 			    PARSE_OPT_OPTARG, thread_callback },
+		OPT_STRING(0, "comment-notes", &comment_notes,
+			   "ref", "use notes from <ref> for comment section"),
 		OPT_END()
 	};
 
@@ -1039,6 +1046,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	if (keep_subject && subject_prefix)
 		die ("--subject-prefix and -k are mutually exclusive.");
 
+	if (comment_notes) {
+		init_notes(NULL, comment_notes, combine_notes_ignore, 0);
+		rev.show_notes = 1;
+	}
+
 	argc = setup_revisions(argc, argv, &rev, "HEAD");
 	if (argc > 1)
 		die ("unrecognized argument: %s", argv[1]);
diff --git a/log-tree.c b/log-tree.c
index d3ae969..9830be8 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -7,6 +7,7 @@
 #include "reflog-walk.h"
 #include "refs.h"
 #include "string-list.h"
+#include "notes.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -457,6 +458,7 @@ int log_tree_diff_flush(struct rev_info *opt)
 	}
 
 	if (opt->loginfo && !opt->no_commit_id) {
+		const unsigned char *sha1 = opt->loginfo->commit->object.sha1;
 		/* When showing a verbose header (i.e. log message),
 		 * and not in --pretty=oneline format, we would want
 		 * an extra newline between the end of log and the
@@ -467,10 +469,20 @@ int log_tree_diff_flush(struct rev_info *opt)
 		    opt->verbose_header &&
 		    opt->commit_format != CMIT_FMT_ONELINE) {
 			int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
-			if ((pch & opt->diffopt.output_format) == pch)
+			if ((pch & opt->diffopt.output_format) == pch
+			    || (opt->commit_format == CMIT_FMT_EMAIL
+				&& opt->show_notes))
 				printf("---");
 			putchar('\n');
 		}
+		if (opt->commit_format == CMIT_FMT_EMAIL && opt->show_notes) {
+			struct strbuf sb = STRBUF_INIT;
+			putchar('\n');
+			format_note(NULL, sha1, &sb, NULL, 0);
+			fwrite(sb.buf, 1, sb.len, stdout);
+			strbuf_release(&sb);
+			putchar('\n');
+		}
 	}
 	diff_flush(&opt->diffopt);
 	return 1;
diff --git a/pretty.c b/pretty.c
index 6ba3da8..10d7812 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1095,7 +1095,7 @@ 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 (context->show_notes)
+	if (context->show_notes && fmt != CMIT_FMT_EMAIL)
 		format_display_notes(commit->object.sha1, sb, encoding,
 				     NOTES_SHOW_HEADER | NOTES_INDENT);
 
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index f2a2aaa..653e50a 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -557,4 +557,95 @@ test_expect_success 'format-patch -- <path>' '
 	! grep "Use .--" error
 '
 
+cat >expect-no-comment <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+ file |   32 --------------------------------
+ foo  |   32 ++++++++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+), 32 deletions(-)
+ delete mode 100644 file
+ create mode 100644 foo
+
+diff --git a/file b/file
+EOF
+
+cat >expect-comment <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+
+a bit of testing here
+
+ file |   32 --------------------------------
+ foo  |   32 ++++++++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+), 32 deletions(-)
+ delete mode 100644 file
+ create mode 100644 foo
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'comment section untouched by default' '
+	git notes add -m"a bit of testing here" &&
+	git format-patch -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-no-comment output+
+'
+
+test_expect_success '--comment-notes' '
+	git format-patch --comment-notes=refs/notes/commits -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+test_expect_success 'format.commentNotes' '
+	git config format.commentNotes refs/notes/commits &&
+	git format-patch -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+test_expect_success '--comment-notes overrides format.commentNotes' '
+	git config format.commentNotes refs/notes/empty &&
+	git format-patch --comment-notes=refs/notes/commits -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+cat >expect <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'no separator with neither diffstat nor notes' '
+	git config --unset format.commentNotes &&
+	git format-patch -p -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect output+
+'
+
+cat >expect <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+
+a bit of testing here
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'separator with notes but no diffstat' '
+	git config format.commentNotes refs/notes/commits &&
+	git format-patch -p -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect output+
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH] BROKEN -- format-patch: learn to fill comment section of email from notes
  2010-02-23 17:34             ` [PATCH] format-patch: learn to fill comment section of email from notes Thomas Rast
@ 2010-02-23 17:34               ` Thomas Rast
  2010-02-23 17:37                 ` Thomas Rast
  2010-02-23 21:56               ` [PATCH] " Junio C Hamano
  2010-03-10 14:08               ` Thomas Rast
  2 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23 17:34 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

Teach git-format-patch a new option --comment-notes (with config
format.commentNotes) which takes a notes ref argument.  These notes
are then added to the patch email between the --- separator and the
diffstat/diff.

This is somewhat tricky because the pretty-printing code has no
control whatsoever over the --- and the diffstat.  To fix that, move
the associated code down into show_log(), which can set options in the
pretty_print_context.  Extend the latter with fields for an arbitrary
string, to be inserted after commit message and after notes.  Use
those to place the --- appropriately.

To ensure correctness of the resulting email, extend the existing rule
(separator if not 'oneline' format and --patch-with-stat) to also
include a separator if there are notes and a patch will be included.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/config.txt           |    6 ++
 Documentation/git-format-patch.txt |   10 ++++
 builtin-log.c                      |   12 +++++
 commit.h                           |    2 +
 log-tree.c                         |   38 ++++++++++-----
 pretty.c                           |   19 ++++++-
 t/t4014-format-patch.sh            |   91 ++++++++++++++++++++++++++++++++++++
 7 files changed, 162 insertions(+), 16 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index dffe1f6..a9e27db 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -886,6 +886,12 @@ format.signoff::
     the rights to submit this work under the same open source license.
     Please see the 'SubmittingPatches' document for further discussion.
 
+format.commentNotes::
+	The default notes reference that is used for the comment
+	section with --pretty=email and for 'git-format-patch'.  (The
+	comment section is the area between the `\---` separator and
+	the patch, which also holds the diffstat.)
+
 gc.aggressiveWindow::
 	The window size parameter used in the delta compression
 	algorithm used by 'git gc --aggressive'.  This defaults
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 9674f9d..afe7e41 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -198,6 +198,16 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 	range are always formatted as creation patches, independently
 	of this flag.
 
+--comment-notes=<ref>::
+	Use the notes from <ref> to fill the comment section of the
+	email, i.e., the part between the `\---` separator and the
+	patch.  See linkgit:git-notes[1].
++
+Warning: the code currently does not guard against a line in the notes
+that starts with `diff`, which will be treated as the start of the
+patch by 'git-am'.
+
+
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
diff --git a/builtin-log.c b/builtin-log.c
index e7ea088..f85837d 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -19,6 +19,7 @@
 #include "remote.h"
 #include "string-list.h"
 #include "parse-options.h"
+#include "notes.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -495,6 +496,7 @@ static void add_header(const char *value)
 #define THREAD_DEEP 2
 static int thread = 0;
 static int do_signoff = 0;
+static const char *comment_notes = NULL;
 
 static int git_format_config(const char *var, const char *value, void *cb)
 {
@@ -548,6 +550,9 @@ static int git_format_config(const char *var, const char *value, void *cb)
 		do_signoff = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "format.commentnotes")) {
+		return git_config_string(&comment_notes, var, value);
+	}
 
 	return git_log_config(var, value, cb);
 }
@@ -955,6 +960,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "thread", &thread, "style",
 			    "enable message threading, styles: shallow, deep",
 			    PARSE_OPT_OPTARG, thread_callback },
+		OPT_STRING(0, "comment-notes", &comment_notes,
+			   "ref", "use notes from <ref> for comment section"),
 		OPT_END()
 	};
 
@@ -1039,6 +1046,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	if (keep_subject && subject_prefix)
 		die ("--subject-prefix and -k are mutually exclusive.");
 
+	if (comment_notes) {
+		init_notes(NULL, comment_notes, combine_notes_ignore, 0);
+		rev.show_notes = 1;
+	}
+
 	argc = setup_revisions(argc, argv, &rev, "HEAD");
 	if (argc > 1)
 		die ("unrecognized argument: %s", argv[1]);
diff --git a/commit.h b/commit.h
index 3cf5166..44bcf5a 100644
--- a/commit.h
+++ b/commit.h
@@ -68,6 +68,8 @@ struct pretty_print_context
 	int abbrev;
 	const char *subject;
 	const char *after_subject;
+	const char *after_message;
+	const char *after_notes;
 	enum date_mode date_mode;
 	int need_8bit_cte;
 	int show_notes;
diff --git a/log-tree.c b/log-tree.c
index d3ae969..93e0f48 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -7,6 +7,7 @@
 #include "reflog-walk.h"
 #include "refs.h"
 #include "string-list.h"
+#include "notes.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -413,6 +414,30 @@ void show_log(struct rev_info *opt)
 	ctx.abbrev = opt->diffopt.abbrev;
 	ctx.after_subject = extra_headers;
 	ctx.reflog_info = opt->reflog_info;
+
+	ctx.after_message = NULL;
+	/*
+	 * In formats other than 'oneline', add an extra newline.  In
+	 * addition, add the triple-dash separator between message and
+	 * diff if there is a diffstat coming or we are showing notes
+	 * in 'email' format.  Otherwise, 'git am' would include the
+	 * diffstat or notes in the commit message when applying the
+	 * commit.
+	 */
+	if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
+	    opt->verbose_header &&
+	    opt->commit_format != CMIT_FMT_ONELINE) {
+		int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+		int patch_with_stat = (pch & opt->diffopt.output_format) == pch;
+		if (opt->commit_format == CMIT_FMT_EMAIL
+		    && (opt->show_notes || patch_with_stat))
+			ctx.after_message = "---\n";
+		else if (opt->commit_format != CMIT_FMT_EMAIL && patch_with_stat)
+			ctx.after_notes = "---\n";
+		else
+			ctx.after_notes = "\n";
+	}
+
 	pretty_print_commit(opt->commit_format, commit, &msgbuf, &ctx);
 
 	if (opt->add_signoff)
@@ -457,20 +482,7 @@ int log_tree_diff_flush(struct rev_info *opt)
 	}
 
 	if (opt->loginfo && !opt->no_commit_id) {
-		/* When showing a verbose header (i.e. log message),
-		 * and not in --pretty=oneline format, we would want
-		 * an extra newline between the end of log and the
-		 * output for readability.
-		 */
 		show_log(opt);
-		if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
-		    opt->verbose_header &&
-		    opt->commit_format != CMIT_FMT_ONELINE) {
-			int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
-			if ((pch & opt->diffopt.output_format) == pch)
-				printf("---");
-			putchar('\n');
-		}
 	}
 	diff_flush(&opt->diffopt);
 	return 1;
diff --git a/pretty.c b/pretty.c
index 6ba3da8..4fc855e 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1095,9 +1095,22 @@ 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 (context->show_notes)
-		format_display_notes(commit->object.sha1, sb, encoding,
-				     NOTES_SHOW_HEADER | NOTES_INDENT);
+	if (context->after_message)
+		strbuf_addstr(sb, context->after_message);
+
+	if (context->show_notes) {
+		if (fmt == CMIT_FMT_EMAIL) {
+			strbuf_addch(sb, '\n');
+			format_note(NULL, commit->object.sha1, sb, encoding, 0);
+			strbuf_addch(sb, '\n');
+		} else {
+			format_display_notes(commit->object.sha1, sb, encoding,
+					     NOTES_SHOW_HEADER | NOTES_INDENT);
+		}
+	}
+
+	if (context->after_notes)
+		strbuf_addstr(sb, context->after_notes);
 
 	free(reencoded);
 }
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index f2a2aaa..653e50a 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -557,4 +557,95 @@ test_expect_success 'format-patch -- <path>' '
 	! grep "Use .--" error
 '
 
+cat >expect-no-comment <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+ file |   32 --------------------------------
+ foo  |   32 ++++++++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+), 32 deletions(-)
+ delete mode 100644 file
+ create mode 100644 foo
+
+diff --git a/file b/file
+EOF
+
+cat >expect-comment <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+
+a bit of testing here
+
+ file |   32 --------------------------------
+ foo  |   32 ++++++++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+), 32 deletions(-)
+ delete mode 100644 file
+ create mode 100644 foo
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'comment section untouched by default' '
+	git notes add -m"a bit of testing here" &&
+	git format-patch -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-no-comment output+
+'
+
+test_expect_success '--comment-notes' '
+	git format-patch --comment-notes=refs/notes/commits -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+test_expect_success 'format.commentNotes' '
+	git config format.commentNotes refs/notes/commits &&
+	git format-patch -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+test_expect_success '--comment-notes overrides format.commentNotes' '
+	git config format.commentNotes refs/notes/empty &&
+	git format-patch --comment-notes=refs/notes/commits -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect-comment output+
+'
+
+cat >expect <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'no separator with neither diffstat nor notes' '
+	git config --unset format.commentNotes &&
+	git format-patch -p -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect output+
+'
+
+cat >expect <<EOF
+Subject: [PATCH] foo
+Cc: R. E. Cipient <rcipient@example.com>
+
+---
+
+a bit of testing here
+
+diff --git a/file b/file
+EOF
+
+test_expect_success 'separator with notes but no diffstat' '
+	git config format.commentNotes refs/notes/commits &&
+	git format-patch -p -1 --stdout > output &&
+	sed -e "1,/^Date/d" -e "/^diff --git/q" output > output+ &&
+	test_cmp expect output+
+'
+
 test_done
-- 
1.7.0.218.g73a398

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH] BROKEN -- format-patch: learn to fill comment section of email from notes
  2010-02-23 17:34               ` [PATCH] BROKEN -- " Thomas Rast
@ 2010-02-23 17:37                 ` Thomas Rast
  2010-02-24  7:45                   ` Stephen Boyd
  0 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-02-23 17:37 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Johannes Sixt, Johan Herland

On Tuesday 23 February 2010 18:34:56 Thomas Rast wrote:
> [PATCH] BROKEN -- format-patch: learn to fill comment section of email from notes

BTW, I forgot to say: the breakage is visible in either t4013 or
t4014, depending which way you tweak the newlines.

IIRC there's also a problem with this patch and Signed-off-by
handling, which I didn't bother fixing because I couldn't get the
newlines right.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH] format-patch: learn to fill comment section of email from notes
  2010-02-23 17:34             ` [PATCH] format-patch: learn to fill comment section of email from notes Thomas Rast
  2010-02-23 17:34               ` [PATCH] BROKEN -- " Thomas Rast
@ 2010-02-23 21:56               ` Junio C Hamano
  2010-03-10 14:08               ` Thomas Rast
  2 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-23 21:56 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Teach git-format-patch a new option --comment-notes (with config
> format.commentNotes) which takes a notes ref argument.  These notes
> are then added to the patch email between the --- separator and the
> diffstat/diff.

Hmmm, why do I find this an ugly hack?

You already have a nice "format_display_notes()" infrastructure to allow
users to get notes from arbitrary notes namespaces, yet this limits the
user to a single notes namespace.  What was the infrastructure built for
if not used in places like this uniformly?

If the answer is "because notes.displayref is a configuration and it is
cumbersome to change every time", then I don't have a sympathy ;-) as that
is exactly why I said config without command line override is a bad thing.

> diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
> index 9674f9d..afe7e41 100644
> --- a/Documentation/git-format-patch.txt
> +++ b/Documentation/git-format-patch.txt
> @@ -198,6 +198,16 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
>  	range are always formatted as creation patches, independently
>  	of this flag.
>  
> +--comment-notes=<ref>::
> +	Use the notes from <ref> to fill the comment section of the
> +	email, i.e., the part between the `\---` separator and the
> +	patch.  See linkgit:git-notes[1].
> ++
> +Warning: the code currently does not guard against a line in the notes
> +that starts with `diff`, which will be treated as the start of the
> +patch by 'git-am'.

It is customary to indent the material after --- by one place (or more) so
it probably is a good idea to do that for notes as well, if we are going
to put it after the three dash lines.  Notice that diffstat output is
already indented that way ;-).

> @@ -457,6 +458,7 @@ int log_tree_diff_flush(struct rev_info *opt)
>  	}
>  
>  	if (opt->loginfo && !opt->no_commit_id) {
> +		const unsigned char *sha1 = opt->loginfo->commit->object.sha1;
>  		/* When showing a verbose header (i.e. log message),
>  		 * and not in --pretty=oneline format, we would want
>  		 * an extra newline between the end of log and the
> @@ -467,10 +469,20 @@ int log_tree_diff_flush(struct rev_info *opt)
>  		    opt->verbose_header &&
>  		    opt->commit_format != CMIT_FMT_ONELINE) {
>  			int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
> -			if ((pch & opt->diffopt.output_format) == pch)
> +			if ((pch & opt->diffopt.output_format) == pch
> +			    || (opt->commit_format == CMIT_FMT_EMAIL
> +				&& opt->show_notes))

This adds a lot of logic to a code that used to be simple "if we have more
stuff to add after message, delimit with "---\n"".  Perhaps the whole body
of the "if" statement ll. 460- should be made into a static helper
function with a single callsite that a clever compiler would inline.

Also I think you would emit "---" even if the commit in question does not
happen to have note with this code.

I've attached a "how about doing it this way" weatherbaloon patch to be
applied on top of this patch at the end---I didn't bother to indent the
notes text nor change it to use format_display_notes(), but hopefully you
will agree the code structure would be easier to follow this way.

> diff --git a/pretty.c b/pretty.c
> index 6ba3da8..10d7812 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -1095,7 +1095,7 @@ 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 (context->show_notes)
> +	if (context->show_notes && fmt != CMIT_FMT_EMAIL)
>  		format_display_notes(commit->object.sha1, sb, encoding,
>  				     NOTES_SHOW_HEADER | NOTES_INDENT);

This is a good fix to prevent notes from getting injected above the
separator line, regardless of your series, I think.

 log-tree.c |   71 +++++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 47 insertions(+), 24 deletions(-)

diff --git a/log-tree.c b/log-tree.c
index 9baf306..af65d33 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -445,6 +445,52 @@ void show_log(struct rev_info *opt)
 	strbuf_release(&msgbuf);
 }
 
+static void log_tree_after_message(struct rev_info *opt)
+{
+	const int pch = (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_PATCH);
+	struct strbuf sb = STRBUF_INIT;
+
+	/*
+	 * When showing a verbose header (i.e. log message),
+	 * and not in --pretty=oneline format, we would want
+	 * an extra newline between the end of log and the
+	 * output for readability.
+	 */
+	if (! ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
+	       opt->verbose_header &&
+	       opt->commit_format != CMIT_FMT_ONELINE) )
+		return;
+
+	/*
+	 * Prepare notes if any...
+	 */
+	if (opt->commit_format == CMIT_FMT_EMAIL && opt->show_notes)
+		format_note(NULL, opt->loginfo->commit->object.sha1,
+			    &sb, NULL, 0);
+
+	/*
+	 * Will we have something other than the message itself?  If
+	 * so we would need three-dashes.
+	 */
+	if (((pch & opt->diffopt.output_format) == pch) || sb.len)
+		printf("---");
+
+	/*
+	 * Then the promised newline...
+	 */
+	putchar('\n');
+
+	/*
+	 * And finally the notes...
+	 */
+	if (sb.len) {
+		putchar('\n');
+		fwrite(sb.buf, 1, sb.len, stdout);
+		strbuf_release(&sb);
+		putchar('\n');
+	}
+}
+
 int log_tree_diff_flush(struct rev_info *opt)
 {
 	diffcore_std(&opt->diffopt);
@@ -458,31 +504,8 @@ int log_tree_diff_flush(struct rev_info *opt)
 	}
 
 	if (opt->loginfo && !opt->no_commit_id) {
-		const unsigned char *sha1 = opt->loginfo->commit->object.sha1;
-		/* When showing a verbose header (i.e. log message),
-		 * and not in --pretty=oneline format, we would want
-		 * an extra newline between the end of log and the
-		 * output for readability.
-		 */
 		show_log(opt);
-		if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
-		    opt->verbose_header &&
-		    opt->commit_format != CMIT_FMT_ONELINE) {
-			int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
-			if ((pch & opt->diffopt.output_format) == pch
-			    || (opt->commit_format == CMIT_FMT_EMAIL
-				&& opt->show_notes))
-				printf("---");
-			putchar('\n');
-		}
-		if (opt->commit_format == CMIT_FMT_EMAIL && opt->show_notes) {
-			struct strbuf sb = STRBUF_INIT;
-			putchar('\n');
-			format_note(NULL, sha1, &sb, NULL, 0);
-			fwrite(sb.buf, 1, sb.len, stdout);
-			strbuf_release(&sb);
-			putchar('\n');
-		}
+		log_tree_after_message(opt);
 	}
 	diff_flush(&opt->diffopt);
 	return 1;

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 06/11] rebase -i: invoke post-rewrite hook
  2010-02-23  0:42       ` [PATCH v5 06/11] rebase -i: " Thomas Rast
@ 2010-02-24  6:15         ` Junio C Hamano
  0 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-02-24  6:15 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> +test_expect_success 'git rebase -i (unchanged)' '
> +	git reset --hard D &&
> +	clear_hook_input &&
> +	FAKE_LINES="1 2" test_must_fail git rebase -i --onto A B &&

This

> ...
> +test_expect_success 'git rebase -i (skip)' '
> +	git reset --hard D &&
> +	clear_hook_input &&
> +	FAKE_LINES="2" test_must_fail git rebase -i --onto A B &&

and this

> ...
> +test_expect_success 'git rebase -i (squash)' '
> +	git reset --hard D &&
> +	clear_hook_input &&
> +	FAKE_LINES="1 squash 2" test_must_fail git rebase -i --onto A B &&

and this need to be fixed to something like

	( FAKE_LINES=... &&
          export FAKE_LINES &&
          test_must_fail git ... ) &&

for Bourne-shell portability.  test_must_fail is a shell function and the
usual one-shot-assignment-to-export rule does not apply.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH] BROKEN -- format-patch: learn to fill comment section of email from notes
  2010-02-23 17:37                 ` Thomas Rast
@ 2010-02-24  7:45                   ` Stephen Boyd
  0 siblings, 0 replies; 135+ messages in thread
From: Stephen Boyd @ 2010-02-24  7:45 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Junio C Hamano, Johannes Sixt, Johan Herland

On 02/23/2010 09:37 AM, Thomas Rast wrote:
> On Tuesday 23 February 2010 18:34:56 Thomas Rast wrote:
>> [PATCH] BROKEN -- format-patch: learn to fill comment section of email from notes
>
> BTW, I forgot to say: the breakage is visible in either t4013 or
> t4014, depending which way you tweak the newlines.

Last time I tried to touch the code in this area I gave up. It looks
like you're experiencing the same problem where pretty formats cause
problems in the tests. I would love to see this sorted out so I can
resurrect my dead topic "log-tree: always add --- marker when options
are patch and a stat".

Good luck!

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v5 09/11] rebase: support automatic notes copying
  2010-02-23  0:42       ` [PATCH v5 09/11] rebase: support automatic notes copying Thomas Rast
@ 2010-02-25  3:58         ` Junio C Hamano
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-02-25  3:58 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> Luckily, all the support already happens to be there.
>
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

I did my first "rebase -i / commit --amend / rebase --continue" cycle with
this series, and it correctly carried notes I keep track of Message-Id of
original patches with.  Good job.

After looking at the output from "git log refs/notes/amlog", I have a few
observations, though.

 - "git notes add" and friends honor GIT_COMMITER_* and GIT_AUTHOR_* as
   usual.  Since I use post-applypatch hook to record the message-id from
   "git am", I ended up getting my notes written by original submitters.

 - The log messages are mostly useless.  The scripted implementation said
   "Annotate 6d8094a8aa32c30ff39c6b8609acb8d057ccb5e5" which was not the
   most informative, but the new one says "Notes added by 'git notes add'".

I haven't thought things through thoroughly, and I am not complaining, but
it may be worth thinking about what the "commit"-ness of generations of
notes tree _should_ mean.

As we are recording the history of how the set of my notes have grown over
time, I think it is natural to expect that the user can record who is
recording this notes tree at what time for what reason, just like a
regular commit does.

 - "added by 'git notes add'" is just as immaterial as "commit log edited
   with vi".  'copied via notes.rebase.rewrite hook' would make much more
   sense.

 - "git notes add" already uses -m option to take the text used as note
   (i.e. payload of the commit); the user may need a way to affect the
   commit log message to record why the note is added (or modified).

 - As to the determination of committer/author identity, I think what the
   code currently does (i.e. honor the environment and user.* config) is
   perfectly sane, but at the same time I think it would be surprising for
   unsuspecting users.  We may want to advise users about this in the
   documentation.

But the above is merely my personal feeling on the "notes history".

The notes implementation (from the scripted one to the one in 'pu') seems
to take quite a different view and is designed as if aspects other than
the topology and the tree each "commit" object records are not useful at
all, and commits are used to implement the notes history only because the
ancestry might help when we later implement merges of notes histories.  I
think that could also be a _valid_ position to take.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting
  2010-02-25  3:58         ` Junio C Hamano
@ 2010-03-10 14:03           ` Thomas Rast
  2010-03-10 14:03             ` [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
                               ` (13 more replies)
  0 siblings, 14 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:03 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Hi all,

Here's (finally) the reroll.  Sorry for the long wait, I was been
swamped at work up until yesterday :-(

I hope I collected all changes that Junio requested; of note are

* the new interface in notes.h actually comes with comments,

* doc update to git-notes(1) in the patch that introduces
  notes.displayRef

I'm not aware of comments by anyone else, so if I missed them please
point them out.

I did some other tweaks, including the new patch 12 that in particular
avoids creating any new commits for notes refs that exist but did not
have anything copied, if you have copying enabled.


On Thursday 25 February 2010 04:58:45 Junio C Hamano wrote:
>  - The log messages are mostly useless.  The scripted implementation said
>    "Annotate 6d8094a8aa32c30ff39c6b8609acb8d057ccb5e5" which was not the
>    most informative, but the new one says "Notes added by 'git notes add'".

I hesitated making it "more informative" in the direction of "Annotate
$sha1" because the same answer can be found with a simple 'git log
--stat'.  So I consider the "Notes added by '<command>'" to actually
be more useful since it conveys something that is not readily
available.

That being said, I think

>  - "git notes add" already uses -m option to take the text used as note
>    (i.e. payload of the commit); the user may need a way to affect the
>    commit log message to record why the note is added (or modified).

is a good idea, even if only to let people write down which
note-adding script recorded this note.


>  - "git notes add" and friends honor GIT_COMMITER_* and GIT_AUTHOR_* as
>    usual.  Since I use post-applypatch hook to record the message-id from
>    "git am", I ended up getting my notes written by original submitters.
[...]
>  - As to the determination of committer/author identity, I think what the
>    code currently does (i.e. honor the environment and user.* config) is
>    perfectly sane, but at the same time I think it would be surprising for
>    unsuspecting users.  We may want to advise users about this in the
>    documentation.

I added a small patch that writes this and the history recording down
in git-notes(1).  However, I'm hesitating to directly say "and this
will bite you if you use it in post-applypatch"; isn't the latter an
implementation detail of git-am?



Thomas Rast (13):
  test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  Support showing notes from more than one notes tree
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  notes: add shorthand --ref to override GIT_NOTES_REF
  notes: track whether notes_trees were changed at all
  git-notes(1): add a section about the meaning of history

 Documentation/config.txt         |   53 +++++-
 Documentation/git-notes.txt      |   46 ++++-
 Documentation/githooks.txt       |   38 ++++
 Documentation/pretty-options.txt |   11 +-
 builtin.h                        |   18 ++
 builtin/commit.c                 |   45 +++++
 builtin/log.c                    |    2 +
 builtin/notes.c                  |  197 ++++++++++++++++++++-
 cache.h                          |    3 +
 git-am.sh                        |   13 ++
 git-rebase--interactive.sh       |   52 +++++-
 git-rebase.sh                    |    6 +
 notes.c                          |  195 +++++++++++++++++++-
 notes.h                          |   67 +++++++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 ++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  377 +++++++++++++++++++++++++++++++++++++-
 t/t3400-rebase.sh                |   17 ++
 t/t3404-rebase-interactive.sh    |   24 +++
 t/t5407-post-rewrite-hook.sh     |  183 ++++++++++++++++++
 t/t7501-commit.sh                |   12 ++
 t/test-lib.sh                    |    4 +
 25 files changed, 1371 insertions(+), 33 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
@ 2010-03-10 14:03             ` Thomas Rast
  2010-03-11  8:55               ` Johan Herland
  2010-03-10 14:03             ` [PATCH v6 02/13] Support showing notes from more than one notes tree Thomas Rast
                               ` (12 subsequent siblings)
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:03 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

---


 t/test-lib.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index a0e396a..400bc6b 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY
 unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 02/13] Support showing notes from more than one notes tree
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
  2010-03-10 14:03             ` [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
@ 2010-03-10 14:03             ` Thomas Rast
  2010-03-11 10:03               ` Johan Herland
  2010-03-10 14:03             ` [PATCH v6 03/13] Documentation: document post-rewrite hook Thomas Rast
                               ` (11 subsequent siblings)
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:03 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.

Thanks to Junio C Hamano for lots of feedback, which greatly
influenced the design of the entire series and this commit in
particular.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v5
* make display_notes_{refs,trees} static
* foo() -> foo(void)
* assert() that init_display_notes() is only called once
* add invocation comments to the new functions in notes.h
* bring git-notes(1) up to date wrt. notes display

 Documentation/config.txt         |   23 +++++-
 Documentation/git-notes.txt      |   11 ++-
 Documentation/pretty-options.txt |   11 ++-
 builtin/log.c                    |    2 +
 cache.h                          |    1 +
 notes.c                          |  173 ++++++++++++++++++++++++++++++++++++--
 notes.h                          |   57 +++++++++++++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 +++++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  148 ++++++++++++++++++++++++++++++--
 t/test-lib.sh                    |    1 +
 14 files changed, 439 insertions(+), 29 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index d9ee519..0d0aa9c 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -519,10 +519,12 @@ check that makes sure that existing object files will not get overwritten.
 core.notesRef::
 	When showing commit messages, also show notes which are stored in
 	the given ref.  This ref is expected to contain files named
-	after the full SHA-1 of the commit they annotate.
+	after the full SHA-1 of the commit they annotate.  The ref
+	must be fully qualified.
 +
 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
+appended to the commit message, separated by a "Notes (<refname>):"
+line (shortened to "Notes:" in the case of "refs/notes/commits").  If the
 given ref itself does not exist, it is not an error, but means that no
 notes should be printed.
 +
@@ -1341,6 +1343,23 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+	The (fully qualified) refname from which to show notes when
+	showing commit messages.  The value of this variable can be set
+	to a glob, in which case notes from all matching refs will be
+	shown.  You may also specify this configuration variable
+	several times.  A warning will be issued for refs that do not
+	exist, but a glob that does not match any refs is silently
+	ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index bef2f39..95b4bca 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -27,12 +27,13 @@ A typical use of notes is to extend a commit message without having
 to change the commit itself. Such commit notes can be shown by `git log`
 along with the original commit message. 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:".
+message, after an unindented line saying "Notes (<refname>):" (or
+"Notes:" for the default setting).
 
-To disable 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".
+This command always manipulates the notes specified in "core.notesRef"
+(see linkgit:git-config[1]), which can be overridden by GIT_NOTES_REF.
+To change which notes are shown by 'git-log', see the
+"notes.displayRef" configuration.
 
 
 SUBCOMMANDS
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index aa96cae..1d56926 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -30,9 +30,18 @@ people using 80-column terminals.
 	defaults to UTF-8.
 
 --no-notes::
---show-notes::
+--show-notes[=<ref>]::
 	Show the notes (see linkgit:git-notes[1]) that annotate the
 	commit, when showing the commit log message.  This is the default
 	for `git log`, `git show` and `git whatchanged` commands when
 	there is no `--pretty`, `--format` nor `--oneline` option is
 	given on the command line.
++
+With an optional argument, add this ref to the list of notes.  The ref
+is taken to be in `refs/notes/` if it is not qualified.
+
+--[no-]standard-notes::
+	Enable or disable populating the notes ref list from the
+	'core.notesRef' and 'notes.displayRef' variables (or
+	corresponding environment overrides).  See
+	linkgit:git-config[1].
diff --git a/builtin/log.c b/builtin/log.c
index b70d0f7..68d636f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -77,6 +77,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 
 	if (!rev->show_notes_given && !rev->pretty_given)
 		rev->show_notes = 1;
+	if (rev->show_notes)
+		init_display_notes(&rev->notes_opt);
 
 	if (rev->diffopt.pickaxe || rev->diffopt.filter)
 		rev->always_show_header = 0;
diff --git a/cache.h b/cache.h
index ded3090..b25d180 100644
--- a/cache.h
+++ b/cache.h
@@ -387,6 +387,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
 
 /*
  * Repository-local GIT_* environment variables
diff --git a/notes.c b/notes.c
index a4f9926..0d4b892 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+static struct string_list display_notes_refs;
+static struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,76 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+				   int flag, void *cb)
+{
+	struct string_list *refs = cb;
+	if (!unsorted_string_list_has_string(refs, path))
+		string_list_append(path, refs);
+	return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+	if (has_glob_specials(glob)) {
+		for_each_glob_ref(string_list_add_one_ref, glob, list);
+	} else {
+		unsigned char sha1[20];
+		if (get_sha1(glob, sha1))
+			warning("notes ref %s is invalid", glob);
+		if (!unsorted_string_list_has_string(list, glob))
+			string_list_append(glob, list);
+	}
+}
+
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs)
+{
+	struct strbuf globbuf = STRBUF_INIT;
+	struct strbuf **split;
+	int i;
+
+	strbuf_addstr(&globbuf, globs);
+	split = strbuf_split(&globbuf, ':');
+
+	for (i = 0; split[i]; i++) {
+		if (!split[i]->len)
+			continue;
+		if (split[i]->buf[split[i]->len-1] == ':')
+			strbuf_setlen(split[i], split[i]->len-1);
+		string_list_add_refs_by_glob(list, split[i]->buf);
+	}
+
+	strbuf_list_free(split);
+	strbuf_release(&globbuf);
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	int *load_refs = cb;
+
+	if (*load_refs && !strcmp(k, "notes.displayref")) {
+		if (!v)
+			config_error_nonbool(k);
+		string_list_add_refs_by_glob(&display_notes_refs, v);
+		return 0;
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref(void)
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +915,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +939,73 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+struct load_notes_cb_data
+{
+	int counter;
+	struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	struct load_notes_cb_data *c = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	c->trees[c->counter++] = t;
+	return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+	struct notes_tree **trees;
+	struct load_notes_cb_data cb_data;
+	trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
+	cb_data.counter = 0;
+	cb_data.trees = trees;
+	for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+	trees[cb_data.counter] = NULL;
+	return trees;
+}
+
+static int string_list_add_refs_from_list(struct string_list_item *item,
+					  void *cb)
+{
+	struct string_list *list = cb;
+	string_list_add_refs_by_glob(list, item->string);
+	return 0;
+}
+
+void init_display_notes(struct display_notes_opt *opt)
+{
+	char *display_ref_env;
+	int load_config_refs = 0;
+	display_notes_refs.strdup_strings = 1;
+
+	assert(!display_notes_trees);
+
+	if (!opt || !opt->suppress_default_notes) {
+		string_list_append(default_notes_ref(), &display_notes_refs);
+		display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+		if (display_ref_env) {
+			string_list_add_refs_from_colon_sep(&display_notes_refs,
+							    display_ref_env);
+			load_config_refs = 0;
+		} else {
+			load_config_refs = 1;
+		}
+	}
+
+	git_config(notes_display_config, &load_config_refs);
+
+	if (opt && opt->extra_notes_refs)
+		for_each_string_list(string_list_add_refs_from_list,
+				     opt->extra_notes_refs,
+				     &display_notes_refs);
+
+	display_notes_trees = load_notes_trees(&display_notes_refs);
+	string_list_clear(&display_notes_refs, 0);
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,8 +1154,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
-		strbuf_addstr(sb, "\nNotes:\n");
+	if (flags & NOTES_SHOW_HEADER) {
+		const char *ref = t->ref;
+		if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+			strbuf_addstr(sb, "\nNotes:\n");
+		} else {
+			if (!prefixcmp(ref, "refs/"))
+				ref += 5;
+			if (!prefixcmp(ref, "notes/"))
+				ref += 6;
+			strbuf_addf(sb, "\nNotes (%s):\n", ref);
+		}
+	}
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
 		linelen = strchrnul(msg_p, '\n') - msg_p;
@@ -1030,3 +1178,14 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	if (!display_notes_trees)
+		init_display_notes(NULL);
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
diff --git a/notes.h b/notes.h
index bad03cc..db47b67 100644
--- a/notes.h
+++ b/notes.h
@@ -198,4 +198,61 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 		struct strbuf *sb, const char *output_encoding, int flags);
 
+
+struct string_list;
+
+struct display_notes_opt
+{
+	int suppress_default_notes : 1;
+	struct string_list *extra_notes_refs;
+};
+
+/*
+ * Load the notes machinery for displaying several notes trees.
+ *
+ * If 'opt' is not NULL, then it specifies additional settings for the
+ * displaying:
+ *
+ * - suppress_default_notes indicates that the notes from
+ *   core.notesRef and notes.displayRef should not be loaded.
+ *
+ * - extra_notes_refs may contain a list of globs (in the same style
+ *   as notes.displayRef) where notes should be loaded from.
+ */
+void init_display_notes(struct display_notes_opt *opt);
+
+/*
+ * Append notes for the given 'object_sha1' from all trees set up by
+ * init_display_notes() to 'sb'.  The 'flags' are a bitwise
+ * combination of
+ *
+ * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header
+ *
+ * - NOTES_INDENT: indent the notes by 4 places
+ *
+ * init_display_notes() is called implicitly for you if you haven't
+ * already.
+ */
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags);
+
+/*
+ * Load the notes tree from each ref listed in 'refs'.  The output is
+ * an array of notes_tree*, terminated by a NULL.
+ */
+struct notes_tree **load_notes_trees(struct string_list *refs);
+
+/*
+ * Add all refs that match 'glob' to the 'list'.
+ */
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
+/*
+ * Add all refs from a colon-separated glob list 'globs' to the end of
+ * 'list'.  Empty components are ignored.  This helper is used to
+ * parse GIT_NOTES_DISPLAY_REF style environment variables.
+ */
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs);
+
 #endif
diff --git a/pretty.c b/pretty.c
index f999485..6ba3da8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER | NOTES_INDENT);
 
 	free(reencoded);
 }
diff --git a/refs.c b/refs.c
index f3fcbe0..5a860c4 100644
--- a/refs.c
+++ b/refs.c
@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 {
 	struct strbuf real_pattern = STRBUF_INIT;
 	struct ref_filter filter;
-	const char *has_glob_specials;
 	int ret;
 
 	if (!prefix && prefixcmp(pattern, "refs/"))
@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 		strbuf_addstr(&real_pattern, prefix);
 	strbuf_addstr(&real_pattern, pattern);
 
-	has_glob_specials = strpbrk(pattern, "?*[");
-	if (!has_glob_specials) {
+	if (!has_glob_specials(pattern)) {
 		/* Append implied '/' '*' if not present. */
 		if (real_pattern.buf[real_pattern.len - 1] != '/')
 			strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9..4a18b08 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ struct ref_lock {
 extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
 extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
+static inline const char *has_glob_specials(const char *pattern)
+{
+	return strpbrk(pattern, "?*[");
+}
+
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
diff --git a/revision.c b/revision.c
index 29721ec..d6e842e 100644
--- a/revision.c
+++ b/revision.c
@@ -12,6 +12,7 @@
 #include "patch-ids.h"
 #include "decorate.h"
 #include "log-tree.h"
+#include "string-list.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -1191,9 +1192,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--show-notes")) {
 		revs->show_notes = 1;
 		revs->show_notes_given = 1;
+	} else if (!prefixcmp(arg, "--show-notes=")) {
+		struct strbuf buf = STRBUF_INIT;
+		revs->show_notes = 1;
+		revs->show_notes_given = 1;
+		if (!revs->notes_opt.extra_notes_refs)
+			revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
+		if (!prefixcmp(arg+13, "refs/"))
+			/* happy */;
+		else if (!prefixcmp(arg+13, "notes/"))
+			strbuf_addstr(&buf, "refs/");
+		else
+			strbuf_addstr(&buf, "refs/notes/");
+		strbuf_addstr(&buf, arg+13);
+		string_list_append(strbuf_detach(&buf, NULL),
+				   revs->notes_opt.extra_notes_refs);
 	} else if (!strcmp(arg, "--no-notes")) {
 		revs->show_notes = 0;
 		revs->show_notes_given = 1;
+	} else if (!strcmp(arg, "--standard-notes")) {
+		revs->show_notes_given = 1;
+		revs->notes_opt.suppress_default_notes = 0;
+	} else if (!strcmp(arg, "--no-standard-notes")) {
+		revs->notes_opt.suppress_default_notes = 1;
 	} else if (!strcmp(arg, "--oneline")) {
 		revs->verbose_header = 1;
 		get_commit_format("oneline", revs);
diff --git a/revision.h b/revision.h
index a14deef..580f6ec 100644
--- a/revision.h
+++ b/revision.h
@@ -3,6 +3,7 @@
 
 #include "parse-options.h"
 #include "grep.h"
+#include "notes.h"
 
 #define SEEN		(1u<<0)
 #define UNINTERESTING   (1u<<1)
@@ -20,6 +21,7 @@
 
 struct rev_info;
 struct log_info;
+struct string_list;
 
 struct rev_info {
 	/* Starting list */
@@ -126,6 +128,9 @@ struct rev_info {
 	struct reflog_walk_info *reflog_info;
 	struct decoration children;
 	struct decoration merge_simplification;
+
+	/* notes-specific options: which refs to show */
+	struct display_notes_opt notes_opt;
 };
 
 #define REV_TREE_SAME		0
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 37b9687..f6cdb33 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -416,7 +416,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700
 
     6th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -449,7 +449,139 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 	test_cmp expect-not-other output
 '
 
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+
+Notes (other):
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	GIT_NOTES_REF=refs/notes/commits git notes add \
+		-m"replacement for deleted note" HEAD^ &&
+	GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+	git config --unset core.notesRef &&
+	git config notes.displayRef "refs/notes/*" &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+	git config core.notesRef refs/notes/commits &&
+	git config notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+	git config --unset core.notesRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git config --add notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+
+Notes:
+    order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+	git config core.notesRef refs/notes/other &&
+	git config --unset-all notes.displayRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git log -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+	git config --unset-all core.notesRef &&
+	git config --unset-all notes.displayRef &&
+	GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+		git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+	git config notes.displayRef "refs/notes/*" &&
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+	test_cmp expect-none output
+'
+
+test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-commits << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+EOF
+
+test_expect_success '--no-standard-notes' '
+	git log --no-standard-notes --show-notes=commits -1 > output &&
+	test_cmp expect-commits output
+'
+
+test_expect_success '--standard-notes' '
+	git log --no-standard-notes --show-notes=commits \
+		--standard-notes -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success '--show-notes=ref accumulates' '
+	git log --show-notes=other --show-notes=commits \
+		 --no-standard-notes -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+	git config core.notesRef refs/notes/other &&
 	echo "Note on a tree" > expect
 	git notes add -m "Note on a tree" HEAD: &&
 	git notes show HEAD: > actual &&
@@ -473,7 +605,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700
 
     7th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -504,7 +636,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 EOF
 
@@ -534,7 +666,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
@@ -553,7 +685,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -570,7 +702,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -589,7 +721,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -620,7 +752,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 400bc6b..3d026b4 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -55,6 +55,7 @@ unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 03/13] Documentation: document post-rewrite hook
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
  2010-03-10 14:03             ` [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
  2010-03-10 14:03             ` [PATCH v6 02/13] Support showing notes from more than one notes tree Thomas Rast
@ 2010-03-10 14:03             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 04/13] commit --amend: invoke " Thomas Rast
                               ` (10 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:03 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/githooks.txt |   34 ++++++++++++++++++++++++++++++++++
 1 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..a741769 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,40 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+it!).  Its first argument denotes the command it was invoked by:
+currently one of `amend` or `rebase`.  Further command-dependent
+arguments may be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
++
+The commits are guaranteed to be listed in the order that they were
+processed by rebase.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 04/13] commit --amend: invoke post-rewrite hook
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (2 preceding siblings ...)
  2010-03-10 14:03             ` [PATCH v6 03/13] Documentation: document post-rewrite hook Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 05/13] rebase: " Thomas Rast
                               ` (9 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin/commit.c             |   39 +++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin/commit.c b/builtin/commit.c
index f4c7344..0367412 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		run_rewrite_hook(head_sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 92d19f5..c38efc4 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -436,7 +436,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 05/13] rebase: invoke post-rewrite hook
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (3 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 04/13] commit --amend: invoke " Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 06/13] rebase -i: " Thomas Rast
                               ` (8 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 50a292a..9b73e0b 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -593,6 +593,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -600,6 +601,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -783,6 +785,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -791,5 +797,9 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 rm -fr "$dotest"
 git gc --auto
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 06/13] rebase -i: invoke post-rewrite hook
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (4 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 05/13] rebase: " Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
                               ` (7 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v5
* work around one-shot export to test_must_fail

 git-rebase--interactive.sh   |   46 +++++++++++++++++++-
 t/t5407-post-rewrite-hook.sh |  101 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+), 1 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c38efc4..f18a118 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/stopped-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -339,6 +347,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -416,6 +425,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -429,6 +458,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -437,6 +467,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -444,6 +475,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		echo "$1" > "$DOTEST"/stopped-sha
 		make_patch $sha1
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
@@ -500,6 +532,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -528,6 +561,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -562,7 +600,12 @@ skip_unnecessary_picks () {
 		esac
 		echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
 	done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
-	mv -f "$TODO".new "$TODO" ||
+	mv -f "$TODO".new "$TODO" &&
+	case "$(peek_next_command)" in
+	squash|s|fixup|f)
+		record_in_rewritten "$ONTO"
+		;;
+	esac ||
 	die "Could not skip unnecessary pick commands"
 }
 
@@ -676,6 +719,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..f0f91f1 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,105 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+# Helper to work around the lack of one-shot exporting for
+# test_must_fail (as it is a shell function)
+test_fail_interactive_rebase () {
+	(
+		FAKE_LINES="$1" &&
+		shift &&
+		export FAKE_LINES &&
+		test_must_fail git rebase -i "$@"
+	)
+}
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "1 2" --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "2" --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "1 squash 2" --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 fixup 2" git rebase -i B &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 07/13] notes: implement 'git notes copy --stdin'
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (5 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 06/13] rebase -i: " Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-11 10:30               ` Johan Herland
  2010-03-10 14:05             ` [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
                               ` (6 subsequent siblings)
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

Changes since v5
* don't remove note we know doesn't exist

 Documentation/git-notes.txt |   12 ++++++++-
 builtin/notes.c             |   56 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   19 ++++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   34 ++++++++++++++++++++++++++
 5 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 95b4bca..064758b 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -56,6 +56,16 @@ copy::
 	object has none (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin/notes.c b/builtin/notes.c
index feb710a..576a989 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -269,6 +269,46 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	free_notes(t);
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -278,7 +318,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -297,6 +337,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg},
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -345,8 +386,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				return notes_copy_from_stdin(force);
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index 0d4b892..b1b15e9 100644
--- a/notes.c
+++ b/notes.c
@@ -1189,3 +1189,22 @@ void format_display_notes(const unsigned char *object_sha1,
 		format_note(display_notes_trees[i], object_sha1, sb,
 			    output_encoding, flags);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note = get_note(t, from_obj);
+	const unsigned char *existing_note = get_note(t, to_obj);
+
+	if (!force && existing_note)
+		return 1;
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else if (existing_note) {
+		add_note(t, to_obj, null_sha1, combine_fn);
+	}
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index db47b67..e1868d8 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index f6cdb33..29ef0c6 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -777,4 +777,38 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	test_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (6 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-11 10:50               ` Johan Herland
  2010-03-10 14:05             ` [PATCH v6 09/13] rebase: support automatic notes copying Thomas Rast
                               ` (5 subsequent siblings)
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/config.txt    |   30 +++++++
 Documentation/git-notes.txt |    4 +
 Documentation/githooks.txt  |    4 +
 builtin.h                   |   18 ++++
 builtin/notes.c             |  139 +++++++++++++++++++++++++++++--
 cache.h                     |    2 +
 t/t3301-notes.sh            |  195 +++++++++++++++++++++++++++++++++++++++++++
 t/test-lib.sh               |    2 +
 8 files changed, 386 insertions(+), 8 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0d0aa9c..25da59e 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1360,6 +1360,36 @@ The effective value of "core.notesRef" (possibly overridden by
 GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `true`, but see
+	"notes.rewriteRef" below.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+	When copying notes during a rewrite, specifies the (fully
+	qualified) ref whose notes should be copied.  The ref may be a
+	glob, in which case notes in all matching refs will be copied.
+	You may also specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 064758b..77311bd 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -35,6 +35,10 @@ This command always manipulates the notes specified in "core.notesRef"
 To change which notes are shown by 'git-log', see the
 "notes.displayRef" configuration.
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index a741769..7183aa9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin.h b/builtin.h
index cdf9847..8aebe61 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,24 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree **trees;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+	struct string_list *refs;
+	int refs_from_env;
+	int mode_from_env;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/builtin/notes.c b/builtin/notes.c
index 576a989..6c2297a 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -16,6 +16,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static const char * const git_notes_usage[] = {
 	"git notes [list [<object>]]",
@@ -269,14 +270,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+		if (!v)
+			config_error_nonbool(k);
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	} else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+		/* note that a refs/ prefix is implied in the
+		 * underlying for_each_glob_ref */
+		if (!prefixcmp(v, "refs/notes/"))
+			string_list_add_refs_by_glob(c->refs, v);
+		else
+			warning("Refusing to rewrite notes in %s"
+				" (outside of refs/notes/)", v);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+	const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+	c->cmd = cmd;
+	c->enabled = 1;
+	c->combine = combine_notes_concatenate;
+	c->refs = xcalloc(1, sizeof(struct string_list));
+	c->refs->strdup_strings = 1;
+	c->refs_from_env = 0;
+	c->mode_from_env = 0;
+	if (rewrite_mode_env) {
+		c->mode_from_env = 1;
+		c->combine = parse_combine_notes_fn(rewrite_mode_env);
+		if (!c->combine)
+			error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
+			      " value: '%s'", rewrite_mode_env);
+	}
+	if (rewrite_refs_env) {
+		c->refs_from_env = 1;
+		string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+	}
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled || !c->refs->nr) {
+		string_list_clear(c->refs, 0);
+		free(c->refs);
+		free(c);
+		return NULL;
+	}
+	c->trees = load_notes_trees(c->refs);
+	string_list_clear(c->refs, 0);
+	free(c->refs);
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	int ret = 0;
+	int i;
+	for (i = 0; c->trees[i]; i++)
+		ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+	return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	int i;
+	for (i = 0; c->trees[i]; i++) {
+		commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+		free_notes(c->trees[i]);
+	}
+	free(c->trees);
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
+	struct notes_rewrite_cfg *c = NULL;
 	struct notes_tree *t;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -293,7 +401,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -304,8 +416,12 @@ int notes_copy_from_stdin(int force)
 		strbuf_list_free(split);
 	}
 
-	commit_notes(t, "Notes added by 'git notes copy'");
-	free_notes(t);
+	if (!rewrite_cmd) {
+		commit_notes(t, "Notes added by 'git notes copy'");
+		free_notes(t);
+	} else {
+		finish_copy_notes_for_rewrite(c);
+	}
 	return ret;
 }
 
@@ -321,6 +437,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes contents options"),
 		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
@@ -338,6 +455,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -386,6 +505,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -393,12 +516,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				return notes_copy_from_stdin(force);
+				return notes_copy_from_stdin(force, rewrite_cmd);
 			}
 		}
 		if (argc < 3) {
diff --git a/cache.h b/cache.h
index b25d180..32c18b1 100644
--- a/cache.h
+++ b/cache.h
@@ -388,6 +388,8 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 
 /*
  * Repository-local GIT_* environment variables
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 29ef0c6..a4a0b1d 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -811,4 +811,199 @@ test_expect_success 'git notes copy --stdin' '
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewriteMode overwrite &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+	git config notes.rewrite.bar false &&
+	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=bar &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+    append 1
+    append 2
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+	git notes add -f -m"append 1" HEAD^ &&
+	git notes add -f -m"append 2" HEAD^^ &&
+	(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
+	echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
+	git notes remove HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 1
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+	git notes add -f -m"replacement note 1" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 2
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+	git config notes.rewriteMode overwrite &&
+	git notes add -f -m"replacement note 2" HEAD^ &&
+	git config --unset-all notes.rewriteRef &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
+		git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
+	git config notes.rewriteRef refs/notes/other &&
+	git notes add -f -m"replacement note 3" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 3d026b4..c582964 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
 unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 09/13] rebase: support automatic notes copying
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (7 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 10/13] commit --amend: copy notes to the new commit Thomas Rast
                               ` (4 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   17 +++++++++++++++++
 t/t3404-rebase-interactive.sh |   24 ++++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 9b73e0b..1056075 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -797,8 +797,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 rm -fr "$dotest"
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index f18a118..a57f043 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -561,6 +561,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..dbf7dfb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..19668c2 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,28 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+	git reset --hard n3 &&
+	git notes add -m"an earlier note" n2 &&
+	GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+	git notes show > output &&
+	test_cmp expect output
+'
+
 test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 10/13] commit --amend: copy notes to the new commit
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (8 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 09/13] rebase: support automatic notes copying Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-10 14:05             ` [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
                               ` (3 subsequent siblings)
  13 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin/commit.c  |    6 ++++++
 t/t7501-commit.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 0367412..8dd104e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1340,6 +1340,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
+		struct notes_rewrite_cfg *cfg;
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(head_sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..8297cb4 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (9 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 10/13] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-11 10:56               ` Johan Herland
  2010-03-10 14:05             ` [PATCH v6 12/13] notes: track whether notes_trees were changed at all Thomas Rast
                               ` (2 subsequent siblings)
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
hence determines the notes tree that will be manipulated.  It also
DWIMs a refs/notes/ prefix.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/git-notes.txt |    5 +++++
 builtin/notes.c             |   16 ++++++++++++++++
 2 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 77311bd..02a2baf 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -116,6 +116,11 @@ OPTIONS
 	Like '-C', but with '-c' the editor is invoked, so that
 	the user can further edit the note message.
 
+--ref <ref>::
+	Manipulate the notes tree in <ref>.  This overrides both
+	GIT_NOTES_REF and the "core.notesRef" configuration.  The ref
+	is taken to be in `refs/notes/` if it is not qualified.
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/builtin/notes.c b/builtin/notes.c
index 6c2297a..cb30ad0 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -438,6 +438,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	const char *rewrite_cmd = NULL;
+	const char *override_notes_ref = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes contents options"),
 		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
@@ -455,6 +456,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
 		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
@@ -464,6 +467,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 
+	if (override_notes_ref) {
+		struct strbuf sb = STRBUF_INIT;
+		if (!prefixcmp(override_notes_ref, "refs/notes/"))
+			/* we're happy */;
+		else if (!prefixcmp(override_notes_ref, "notes/"))
+			strbuf_addstr(&sb, "refs/");
+		else
+			strbuf_addstr(&sb, "refs/notes/");
+		strbuf_addstr(&sb, override_notes_ref);
+		setenv("GIT_NOTES_REF", sb.buf, 1);
+		strbuf_release(&sb);
+	}
+
 	if (argc && !strcmp(argv[0], "list"))
 		list = 1;
 	else if (argc && !strcmp(argv[0], "add"))
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 12/13] notes: track whether notes_trees were changed at all
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (10 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-03-10 14:05             ` Thomas Rast
  2010-03-11 10:58               ` Johan Herland
  2010-03-10 14:06             ` [PATCH v6 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
  2010-03-10 21:23             ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Junio C Hamano
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:05 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

Currently, the notes copying is a bit wasteful since it always creates
new trees, even if no notes were copied at all.

Teach add_note() and remove_note() to flag the affected notes tree as
changed ('dirty').  Then teach builtin/notes.c to use this knowledge
and avoid committing trees that weren't changed.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

New in this iteration

 builtin/notes.c |    2 ++
 notes.c         |    3 +++
 notes.h         |    1 +
 3 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index cb30ad0..4543d11 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -240,6 +240,8 @@ int commit_notes(struct notes_tree *t, const char *msg)
 		t = &default_notes_tree;
 	if (!t->initialized || !t->ref || !*t->ref)
 		die("Cannot commit uninitialized/unreferenced notes tree");
+	if (!t->dirty)
+		return 0; /* don't have to commit an unchanged tree */
 
 	/* Prepare commit message and reflog message */
 	strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
diff --git a/notes.c b/notes.c
index b1b15e9..ffda597 100644
--- a/notes.c
+++ b/notes.c
@@ -926,6 +926,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	t->ref = notes_ref ? xstrdup(notes_ref) : NULL;
 	t->combine_notes = combine_notes;
 	t->initialized = 1;
+	t->dirty = 0;
 
 	if (flags & NOTES_INIT_EMPTY || !notes_ref ||
 	    read_ref(notes_ref, object_sha1))
@@ -1014,6 +1015,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (!t)
 		t = &default_notes_tree;
 	assert(t->initialized);
+	t->dirty = 1;
 	if (!combine_notes)
 		combine_notes = t->combine_notes;
 	l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
@@ -1029,6 +1031,7 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1)
 	if (!t)
 		t = &default_notes_tree;
 	assert(t->initialized);
+	t->dirty = 1;
 	hashcpy(l.key_sha1, object_sha1);
 	hashclr(l.val_sha1);
 	return note_tree_remove(t, t->root, 0, &l);
diff --git a/notes.h b/notes.h
index e1868d8..ee5d47b 100644
--- a/notes.h
+++ b/notes.h
@@ -40,6 +40,7 @@
 	char *ref;
 	combine_notes_fn *combine_notes;
 	int initialized;
+	int dirty;
 } default_notes_tree;
 
 /*
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v6 13/13] git-notes(1): add a section about the meaning of history
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (11 preceding siblings ...)
  2010-03-10 14:05             ` [PATCH v6 12/13] notes: track whether notes_trees were changed at all Thomas Rast
@ 2010-03-10 14:06             ` Thomas Rast
  2010-03-11 10:59               ` Johan Herland
  2010-03-10 21:23             ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Junio C Hamano
  13 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:06 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

To the displaying code, the only interesting thing about a notes ref
is that it has a tree of the required format.  However, notes actually
have a history since they are recorded as successive commits.

Make a note about the existence of this history in the manpage, but
keep some doors open if we want to change the details.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---

New in v6, as per Junio's comment

>  - As to the determination of committer/author identity, I think what the
>    code currently does (i.e. honor the environment and user.* config) is
>    perfectly sane, but at the same time I think it would be surprising for
>    unsuspecting users.  We may want to advise users about this in the
>    documentation.

(but see the series leader for more discussion about this).

 Documentation/git-notes.txt |   14 ++++++++++++++
 1 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 02a2baf..4e5113b 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -121,6 +121,20 @@ OPTIONS
 	GIT_NOTES_REF and the "core.notesRef" configuration.  The ref
 	is taken to be in `refs/notes/` if it is not qualified.
 
+
+NOTES
+-----
+
+Every notes change creates a new commit at the specified notes ref.
+You can therefore inspect the history of the notes by invoking, e.g.,
+`git log -p notes/commits`.
+
+Currently the commit message only records which operation triggered
+the update, and the commit authorship is determined according to the
+usual rules (see linkgit:git-commit[1]).  These details may change in
+the future.
+
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
-- 
1.7.0.2.407.g21ebda

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH] format-patch: learn to fill comment section of email from notes
  2010-02-23 17:34             ` [PATCH] format-patch: learn to fill comment section of email from notes Thomas Rast
  2010-02-23 17:34               ` [PATCH] BROKEN -- " Thomas Rast
  2010-02-23 21:56               ` [PATCH] " Junio C Hamano
@ 2010-03-10 14:08               ` Thomas Rast
  2 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-10 14:08 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland

I accidentally sent out this and the "BROKEN" patch, they're the exact
ones I posted on Feb 23 and should be disregarded.

Sorry for the noise...

[And this time I can't even claim it was because I was hacking past
midnight :-(]

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting
  2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
                               ` (12 preceding siblings ...)
  2010-03-10 14:06             ` [PATCH v6 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
@ 2010-03-10 21:23             ` Junio C Hamano
  13 siblings, 0 replies; 135+ messages in thread
From: Junio C Hamano @ 2010-03-10 21:23 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt, Johan Herland

Thomas Rast <trast@student.ethz.ch> writes:

> I'm not aware of comments by anyone else, so if I missed them please
> point them out.

Yes, please ;-)

>>  - "git notes add" and friends honor GIT_COMMITER_* and GIT_AUTHOR_* as
>>    usual.  Since I use post-applypatch hook to record the message-id from
>>    "git am", I ended up getting my notes written by original submitters.
> [...]
>>  - As to the determination of committer/author identity, I think what the
>>    code currently does (i.e. honor the environment and user.* config) is
>>    perfectly sane, but at the same time I think it would be surprising for
>>    unsuspecting users.  We may want to advise users about this in the
>>    documentation.
>
> I added a small patch that writes this and the history recording down
> in git-notes(1).  However, I'm hesitating to directly say "and this
> will bite you if you use it in post-applypatch"; isn't the latter an
> implementation detail of git-am?

Probably.  We haven't documented what can and cannot be used by pre- and
post- applypatch, but I think at least post-applypatch should not rely on
the authorship information exported from the caller; if it wants to find
out, it can parse HEAD, and because it is a post-hook, it should always be
correct no matter how git-am implementation will change in the future.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  2010-03-10 14:03             ` [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
@ 2010-03-11  8:55               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11  8:55 UTC (permalink / raw)
  To: git; +Cc: Thomas Rast, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> ---
> 
> 
>  t/test-lib.sh |    1 +
>  1 files changed, 1 insertions(+), 0 deletions(-)
> 
> diff --git a/t/test-lib.sh b/t/test-lib.sh
> index a0e396a..400bc6b 100644
> --- a/t/test-lib.sh
> +++ b/t/test-lib.sh
> @@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY
>  unset GIT_CEILING_DIRECTORIES
>  unset SHA1_FILE_DIRECTORIES
>  unset SHA1_FILE_DIRECTORY
> +unset GIT_NOTES_REF
>  GIT_MERGE_VERBOSITY=5
>  export GIT_MERGE_VERBOSITY
>  export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME

Acked-by: Johan Herland <johan@herland.net>


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 02/13] Support showing notes from more than one notes tree
  2010-03-10 14:03             ` [PATCH v6 02/13] Support showing notes from more than one notes tree Thomas Rast
@ 2010-03-11 10:03               ` Johan Herland
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
  0 siblings, 1 reply; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:03 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> With this patch, you can set notes.displayRef to a glob that points at
> your favourite notes refs, e.g.,
> 
> [notes]
> 	displayRef = refs/notes/*
> 
> Then git-log and friends will show notes from all trees.
> 
> Thanks to Junio C Hamano for lots of feedback, which greatly
> influenced the design of the entire series and this commit in
> particular.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>

...except for the following small niggles:

> diff --git a/Documentation/pretty-options.txt
> b/Documentation/pretty-options.txt index aa96cae..1d56926 100644
> --- a/Documentation/pretty-options.txt
> +++ b/Documentation/pretty-options.txt
> @@ -30,9 +30,18 @@ people using 80-column terminals.
>  	defaults to UTF-8.
> 
>  --no-notes::
> ---show-notes::
> +--show-notes[=<ref>]::
>  	Show the notes (see linkgit:git-notes[1]) that annotate the
>  	commit, when showing the commit log message.  This is the default
>  	for `git log`, `git show` and `git whatchanged` commands when
>  	there is no `--pretty`, `--format` nor `--oneline` option is
>  	given on the command line.
> ++
> +With an optional argument, add this ref to the list of notes.  The ref
> +is taken to be in `refs/notes/` if it is not qualified.
> +
> +--[no-]standard-notes::
> +	Enable or disable populating the notes ref list from the
> +	'core.notesRef' and 'notes.displayRef' variables (or
> +	corresponding environment overrides).  See
> +	linkgit:git-config[1].

Should probably mention that --standard-notes describes the default 
behaviour.

> diff --git a/notes.c b/notes.c
> index a4f9926..0d4b892 100644
> --- a/notes.c
> +++ b/notes.c
> +static int notes_display_config(const char *k, const char *v, void *cb)
> +{
> +	int *load_refs = cb;
> +
> +	if (*load_refs && !strcmp(k, "notes.displayref")) {
> +		if (!v)
> +			config_error_nonbool(k);
> +		string_list_add_refs_by_glob(&display_notes_refs, v);
> +		return 0;

This "return 0" is unnecessary

> +static int string_list_add_refs_from_list(struct string_list_item *item,
> +					  void *cb)
> +{
> +	struct string_list *list = cb;
> +	string_list_add_refs_by_glob(list, item->string);
> +	return 0;
> +}

Shouldn't string_list_add_refs_from_list() be placed up with the other 
string_list_add_* functions? 

> +void init_display_notes(struct display_notes_opt *opt)
> +{
> +	char *display_ref_env;
> +	int load_config_refs = 0;
> +	display_notes_refs.strdup_strings = 1;
> +
> +	assert(!display_notes_trees);
> +
> +	if (!opt || !opt->suppress_default_notes) {
> +		string_list_append(default_notes_ref(), &display_notes_refs);
> +		display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
> +		if (display_ref_env) {
> +			string_list_add_refs_from_colon_sep(&display_notes_refs,
> +							    display_ref_env);
> +			load_config_refs = 0;
> +		} else {
> +			load_config_refs = 1;
> +		}

Drop unnecessary braces in the 'else'

> @@ -1030,3 +1178,14 @@ void format_note(struct notes_tree *t, const
> unsigned char *object_sha1,
> 
>  	free(msg);
>  }
> +
> +void format_display_notes(const unsigned char *object_sha1,
> +			  struct strbuf *sb, const char *output_encoding, int flags)
> +{
> +	int i;
> +	if (!display_notes_trees)
> +		init_display_notes(NULL);

Not sure I like this "fallback" call to init_display_notes(NULL). There's 
already a call from builtin/log.c which passes "&rev->notes_opt", and this 
fallback is at best useless (since init_display_notes() has already been 
called from builtin/log.c), and at worst confusing, since it would _mask_ a 
missing init_display_notes(options) call from elsewhere.

I'd rather suggest that format_display_notes() should fail because of a 
missing init_display_notes(), and not silently compensate for missing 
initialization by self-initializing with default options (thus disregarding 
any notes-related options the user might have passed on the command-line).

> diff --git a/notes.h b/notes.h
> index bad03cc..db47b67 100644
> --- a/notes.h
> +++ b/notes.h
> +/*
> + * Append notes for the given 'object_sha1' from all trees set up by
> + * init_display_notes() to 'sb'.  The 'flags' are a bitwise
> + * combination of
> + *
> + * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header
> + *
> + * - NOTES_INDENT: indent the notes by 4 places
> + *
> + * init_display_notes() is called implicitly for you if you haven't
> + * already.

As I said above, the implicit initialization turns a missing 
init_display_notes(with_approriate_options) call into a 
init_display_notes(NULL) call. This makes it harder to pinpoint future bugs 
in this area (which are likely to crop up as we add notes functionality to 
more Git commands).

But I don't think this is important enough to hold up the patch series.

> + */
> +void format_display_notes(const unsigned char *object_sha1,
> +			  struct strbuf *sb, const char *output_encoding, int flags);
> +
> +/*
> + * Load the notes tree from each ref listed in 'refs'.  The output is
> + * an array of notes_tree*, terminated by a NULL.
> + */
> +struct notes_tree **load_notes_trees(struct string_list *refs);

Although this looks like a useful utility function, I'm not sure how useful 
it is in practice: What would you really _do_ with the returned struct 
notes_tree **? You can't pass it to format_display_notes() (which only uses 
the internal display_notes_trees)...

> +/*
> + * Add all refs that match 'glob' to the 'list'.
> + */
> +void string_list_add_refs_by_glob(struct string_list *list, const char
> *glob); +
> +/*
> + * Add all refs from a colon-separated glob list 'globs' to the end of
> + * 'list'.  Empty components are ignored.  This helper is used to
> + * parse GIT_NOTES_DISPLAY_REF style environment variables.
> + */
> +void string_list_add_refs_from_colon_sep(struct string_list *list,
> +					 const char *globs);

The same goes for these. Do they have actual outside callers? AFAICS we only 
ever use the internal display_notes_refs and display_notes_trees lists in 
notes.c, so there's limited potential for external use.


Otherwise, it all looks good to me. :)


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 07/13] notes: implement 'git notes copy --stdin'
  2010-03-10 14:05             ` [PATCH v6 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-03-11 10:30               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:30 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> This implements a mass-copy command that takes a sequence of lines in
> the format
> 
>   <from-sha1> SP <to-sha1> [ SP <rest> ] LF
> 
> on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
> <rest> is ignored.  The intent, of course, is that this can read the
> same input that the 'post-rewrite' hook gets.
> 
> The copy_note() function is exposed for everyone's and in particular
> the next commit's use.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>

> ---
> 
> Changes since v5
> * don't remove note we know doesn't exist

[...]

> diff --git a/notes.c b/notes.c
> index 0d4b892..b1b15e9 100644
> --- a/notes.c
> +++ b/notes.c
> @@ -1189,3 +1189,22 @@ void format_display_notes(const unsigned char
> *object_sha1, format_note(display_notes_trees[i], object_sha1, sb,
>  			    output_encoding, flags);
>  }
> +
> +int copy_note(struct notes_tree *t,
> +	      const unsigned char *from_obj, const unsigned char *to_obj,
> +	      int force, combine_notes_fn combine_fn)
> +{
> +	const unsigned char *note = get_note(t, from_obj);
> +	const unsigned char *existing_note = get_note(t, to_obj);
> +
> +	if (!force && existing_note)
> +		return 1;
> +
> +	if (note)
> +		add_note(t, to_obj, note, combine_fn);
> +	else if (existing_note) {
> +		add_note(t, to_obj, null_sha1, combine_fn);
> +	}

You might want to lose the braces around the second add_note().


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite
  2010-03-10 14:05             ` [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-03-11 10:50               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:50 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> Implement helper functions to load the rewriting config, and to
> actually copy the notes.  Also document the config.
> 
> Secondly, also implement an undocumented --for-rewrite=<cmd> option to
> 'git notes copy' which is used like --stdin, but also puts the
> configuration for <cmd> into effect.  It will be needed to support the
> copying in git-rebase.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>

[...]

> diff --git a/builtin/notes.c b/builtin/notes.c
> index 576a989..6c2297a 100644
> --- a/builtin/notes.c
> +++ b/builtin/notes.c

[...]

> +struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
> +{
> +	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct
> notes_rewrite_cfg)); +	const char *rewrite_mode_env =
> getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); +	const char
> *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); +	c->cmd
> = cmd;
> +	c->enabled = 1;
> +	c->combine = combine_notes_concatenate;
> +	c->refs = xcalloc(1, sizeof(struct string_list));
> +	c->refs->strdup_strings = 1;
> +	c->refs_from_env = 0;
> +	c->mode_from_env = 0;
> +	if (rewrite_mode_env) {
> +		c->mode_from_env = 1;
> +		c->combine = parse_combine_notes_fn(rewrite_mode_env);
> +		if (!c->combine)
> +			error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
> +			      " value: '%s'", rewrite_mode_env);
> +	}
> +	if (rewrite_refs_env) {
> +		c->refs_from_env = 1;
> +		string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
> +	}
> +	git_config(notes_rewrite_config, c);
> +	if (!c->enabled || !c->refs->nr) {
> +		string_list_clear(c->refs, 0);
> +		free(c->refs);
> +		free(c);
> +		return NULL;
> +	}
> +	c->trees = load_notes_trees(c->refs);

Ah, this invalidates some of my comments to PATCH 02/13. Please disregard 
those comments.


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-03-10 14:05             ` [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-03-11 10:56               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:56 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
> hence determines the notes tree that will be manipulated.  It also
> DWIMs a refs/notes/ prefix.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 12/13] notes: track whether notes_trees were changed at all
  2010-03-10 14:05             ` [PATCH v6 12/13] notes: track whether notes_trees were changed at all Thomas Rast
@ 2010-03-11 10:58               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:58 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> Currently, the notes copying is a bit wasteful since it always creates
> new trees, even if no notes were copied at all.
> 
> Teach add_note() and remove_note() to flag the affected notes tree as
> changed ('dirty').  Then teach builtin/notes.c to use this knowledge
> and avoid committing trees that weren't changed.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>


-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH v6 13/13] git-notes(1): add a section about the meaning of history
  2010-03-10 14:06             ` [PATCH v6 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
@ 2010-03-11 10:59               ` Johan Herland
  0 siblings, 0 replies; 135+ messages in thread
From: Johan Herland @ 2010-03-11 10:59 UTC (permalink / raw)
  To: Thomas Rast; +Cc: git, Johannes Sixt

On Wednesday 10 March 2010, Thomas Rast wrote:
> To the displaying code, the only interesting thing about a notes ref
> is that it has a tree of the required format.  However, notes actually
> have a history since they are recorded as successive commits.
> 
> Make a note about the existence of this history in the manpage, but
> keep some doors open if we want to change the details.
> 
> Signed-off-by: Thomas Rast <trast@student.ethz.ch>

Acked-by: Johan Herland <johan@herland.net>

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v7 00/13] tr/display-notes
  2010-03-11 10:03               ` Johan Herland
@ 2010-03-12 17:04                 ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
                                     ` (12 more replies)
  0 siblings, 13 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Here's a hopefully last replacement for the series.  I implemented
Johan's requests for changes and collected his acks; the only
behavioral change is that format_display_notes() no longer calls
init_display_notes() implicitly.

Thomas Rast (13):
  test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  Support showing notes from more than one notes tree
  Documentation: document post-rewrite hook
  commit --amend: invoke post-rewrite hook
  rebase: invoke post-rewrite hook
  rebase -i: invoke post-rewrite hook
  notes: implement 'git notes copy --stdin'
  notes: implement helpers needed for note copying during rewrite
  rebase: support automatic notes copying
  commit --amend: copy notes to the new commit
  notes: add shorthand --ref to override GIT_NOTES_REF
  notes: track whether notes_trees were changed at all
  git-notes(1): add a section about the meaning of history

 Documentation/config.txt         |   53 +++++-
 Documentation/git-notes.txt      |   46 ++++-
 Documentation/githooks.txt       |   38 ++++
 Documentation/pretty-options.txt |   11 +-
 builtin.h                        |   18 ++
 builtin/commit.c                 |   45 +++++
 builtin/log.c                    |    5 +
 builtin/notes.c                  |  197 ++++++++++++++++++++-
 cache.h                          |    3 +
 git-am.sh                        |   13 ++
 git-rebase--interactive.sh       |   52 +++++-
 git-rebase.sh                    |    6 +
 notes.c                          |  191 +++++++++++++++++++-
 notes.h                          |   66 +++++++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 ++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  377 +++++++++++++++++++++++++++++++++++++-
 t/t3400-rebase.sh                |   17 ++
 t/t3404-rebase-interactive.sh    |   24 +++
 t/t5407-post-rewrite-hook.sh     |  183 ++++++++++++++++++
 t/t7501-commit.sh                |   12 ++
 t/test-lib.sh                    |    4 +
 25 files changed, 1369 insertions(+), 33 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

^ permalink raw reply	[flat|nested] 135+ messages in thread

* [PATCH v7 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 02/13] Support showing notes from more than one notes tree Thomas Rast
                                     ` (11 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 t/test-lib.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/t/test-lib.sh b/t/test-lib.sh
index a0e396a..400bc6b 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -54,6 +54,7 @@ unset GIT_OBJECT_DIRECTORY
 unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+unset GIT_NOTES_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 02/13] Support showing notes from more than one notes tree
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 03/13] Documentation: document post-rewrite hook Thomas Rast
                                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

With this patch, you can set notes.displayRef to a glob that points at
your favourite notes refs, e.g.,

[notes]
	displayRef = refs/notes/*

Then git-log and friends will show notes from all trees.

Thanks to Junio C Hamano for lots of feedback, which greatly
influenced the design of the entire series and this commit in
particular.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---

* don't silently init from format_display_notes

 Documentation/config.txt         |   23 +++++-
 Documentation/git-notes.txt      |   11 ++-
 Documentation/pretty-options.txt |   11 ++-
 builtin/log.c                    |    5 +
 cache.h                          |    1 +
 notes.c                          |  170 ++++++++++++++++++++++++++++++++++++--
 notes.h                          |   56 +++++++++++++
 pretty.c                         |    6 +-
 refs.c                           |    4 +-
 refs.h                           |    5 +
 revision.c                       |   21 +++++
 revision.h                       |    5 +
 t/t3301-notes.sh                 |  148 +++++++++++++++++++++++++++++++--
 t/test-lib.sh                    |    1 +
 14 files changed, 438 insertions(+), 29 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index d9ee519..0d0aa9c 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -519,10 +519,12 @@ check that makes sure that existing object files will not get overwritten.
 core.notesRef::
 	When showing commit messages, also show notes which are stored in
 	the given ref.  This ref is expected to contain files named
-	after the full SHA-1 of the commit they annotate.
+	after the full SHA-1 of the commit they annotate.  The ref
+	must be fully qualified.
 +
 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
+appended to the commit message, separated by a "Notes (<refname>):"
+line (shortened to "Notes:" in the case of "refs/notes/commits").  If the
 given ref itself does not exist, it is not an error, but means that no
 notes should be printed.
 +
@@ -1341,6 +1343,23 @@ mergetool.keepTemporaries::
 mergetool.prompt::
 	Prompt before each invocation of the merge resolution program.
 
+notes.displayRef::
+	The (fully qualified) refname from which to show notes when
+	showing commit messages.  The value of this variable can be set
+	to a glob, in which case notes from all matching refs will be
+	shown.  You may also specify this configuration variable
+	several times.  A warning will be issued for refs that do not
+	exist, but a glob that does not match any refs is silently
+	ignored.
++
+This setting can be overridden with the `GIT_NOTES_DISPLAY_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
++
+The effective value of "core.notesRef" (possibly overridden by
+GIT_NOTES_REF) is also implicitly added to the list of refs to be
+displayed.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index bef2f39..95b4bca 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -27,12 +27,13 @@ A typical use of notes is to extend a commit message without having
 to change the commit itself. Such commit notes can be shown by `git log`
 along with the original commit message. 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:".
+message, after an unindented line saying "Notes (<refname>):" (or
+"Notes:" for the default setting).
 
-To disable 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".
+This command always manipulates the notes specified in "core.notesRef"
+(see linkgit:git-config[1]), which can be overridden by GIT_NOTES_REF.
+To change which notes are shown by 'git-log', see the
+"notes.displayRef" configuration.
 
 
 SUBCOMMANDS
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
index aa96cae..af6d2b9 100644
--- a/Documentation/pretty-options.txt
+++ b/Documentation/pretty-options.txt
@@ -30,9 +30,18 @@ people using 80-column terminals.
 	defaults to UTF-8.
 
 --no-notes::
---show-notes::
+--show-notes[=<ref>]::
 	Show the notes (see linkgit:git-notes[1]) that annotate the
 	commit, when showing the commit log message.  This is the default
 	for `git log`, `git show` and `git whatchanged` commands when
 	there is no `--pretty`, `--format` nor `--oneline` option is
 	given on the command line.
++
+With an optional argument, add this ref to the list of notes.  The ref
+is taken to be in `refs/notes/` if it is not qualified.
+
+--[no-]standard-notes::
+	Enable or disable populating the notes ref list from the
+	'core.notesRef' and 'notes.displayRef' variables (or
+	corresponding environment overrides).  Enabled by default.
+	See linkgit:git-config[1].
diff --git a/builtin/log.c b/builtin/log.c
index b70d0f7..9feb031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -77,6 +77,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
 
 	if (!rev->show_notes_given && !rev->pretty_given)
 		rev->show_notes = 1;
+	if (rev->show_notes)
+		init_display_notes(&rev->notes_opt);
 
 	if (rev->diffopt.pickaxe || rev->diffopt.filter)
 		rev->always_show_header = 0;
@@ -1100,6 +1102,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
 		DIFF_OPT_SET(&rev.diffopt, BINARY);
 
+	if (rev.show_notes)
+		init_display_notes(&rev.notes_opt);
+
 	if (!use_stdout)
 		output_directory = set_outdir(prefix, output_directory);
 
diff --git a/cache.h b/cache.h
index ded3090..b25d180 100644
--- a/cache.h
+++ b/cache.h
@@ -387,6 +387,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
 
 /*
  * Repository-local GIT_* environment variables
diff --git a/notes.c b/notes.c
index a4f9926..216ad24 100644
--- a/notes.c
+++ b/notes.c
@@ -5,6 +5,8 @@
 #include "utf8.h"
 #include "strbuf.h"
 #include "tree-walk.h"
+#include "string-list.h"
+#include "refs.h"
 
 /*
  * Use a non-balancing simple 16-tree structure with struct int_node as
@@ -68,6 +70,9 @@ struct non_note {
 
 struct notes_tree default_notes_tree;
 
+static struct string_list display_notes_refs;
+static struct notes_tree **display_notes_trees;
+
 static void load_subtree(struct notes_tree *t, struct leaf_node *subtree,
 		struct int_node *node, unsigned int n);
 
@@ -828,6 +833,83 @@ int combine_notes_ignore(unsigned char *cur_sha1,
 	return 0;
 }
 
+static int string_list_add_one_ref(const char *path, const unsigned char *sha1,
+				   int flag, void *cb)
+{
+	struct string_list *refs = cb;
+	if (!unsorted_string_list_has_string(refs, path))
+		string_list_append(path, refs);
+	return 0;
+}
+
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
+{
+	if (has_glob_specials(glob)) {
+		for_each_glob_ref(string_list_add_one_ref, glob, list);
+	} else {
+		unsigned char sha1[20];
+		if (get_sha1(glob, sha1))
+			warning("notes ref %s is invalid", glob);
+		if (!unsorted_string_list_has_string(list, glob))
+			string_list_append(glob, list);
+	}
+}
+
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs)
+{
+	struct strbuf globbuf = STRBUF_INIT;
+	struct strbuf **split;
+	int i;
+
+	strbuf_addstr(&globbuf, globs);
+	split = strbuf_split(&globbuf, ':');
+
+	for (i = 0; split[i]; i++) {
+		if (!split[i]->len)
+			continue;
+		if (split[i]->buf[split[i]->len-1] == ':')
+			strbuf_setlen(split[i], split[i]->len-1);
+		string_list_add_refs_by_glob(list, split[i]->buf);
+	}
+
+	strbuf_list_free(split);
+	strbuf_release(&globbuf);
+}
+
+static int string_list_add_refs_from_list(struct string_list_item *item,
+					  void *cb)
+{
+	struct string_list *list = cb;
+	string_list_add_refs_by_glob(list, item->string);
+	return 0;
+}
+
+static int notes_display_config(const char *k, const char *v, void *cb)
+{
+	int *load_refs = cb;
+
+	if (*load_refs && !strcmp(k, "notes.displayref")) {
+		if (!v)
+			config_error_nonbool(k);
+		string_list_add_refs_by_glob(&display_notes_refs, v);
+	}
+
+	return 0;
+}
+
+static const char *default_notes_ref(void)
+{
+	const char *notes_ref = NULL;
+	if (!notes_ref)
+		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
+	if (!notes_ref)
+		notes_ref = notes_ref_name; /* value of core.notesRef config */
+	if (!notes_ref)
+		notes_ref = GIT_NOTES_DEFAULT_REF;
+	return notes_ref;
+}
+
 void init_notes(struct notes_tree *t, const char *notes_ref,
 		combine_notes_fn combine_notes, int flags)
 {
@@ -840,11 +922,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	assert(!t->initialized);
 
 	if (!notes_ref)
-		notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT);
-	if (!notes_ref)
-		notes_ref = notes_ref_name; /* value of core.notesRef config */
-	if (!notes_ref)
-		notes_ref = GIT_NOTES_DEFAULT_REF;
+		notes_ref = default_notes_ref();
 
 	if (!combine_notes)
 		combine_notes = combine_notes_concatenate;
@@ -868,6 +946,64 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	load_subtree(t, &root_tree, t->root, 0);
 }
 
+struct load_notes_cb_data
+{
+	int counter;
+	struct notes_tree **trees;
+};
+
+static int load_one_display_note_ref(struct string_list_item *item,
+				     void *cb_data)
+{
+	struct load_notes_cb_data *c = cb_data;
+	struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree));
+	init_notes(t, item->string, combine_notes_ignore, 0);
+	c->trees[c->counter++] = t;
+	return 0;
+}
+
+struct notes_tree **load_notes_trees(struct string_list *refs)
+{
+	struct notes_tree **trees;
+	struct load_notes_cb_data cb_data;
+	trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *));
+	cb_data.counter = 0;
+	cb_data.trees = trees;
+	for_each_string_list(load_one_display_note_ref, refs, &cb_data);
+	trees[cb_data.counter] = NULL;
+	return trees;
+}
+
+void init_display_notes(struct display_notes_opt *opt)
+{
+	char *display_ref_env;
+	int load_config_refs = 0;
+	display_notes_refs.strdup_strings = 1;
+
+	assert(!display_notes_trees);
+
+	if (!opt || !opt->suppress_default_notes) {
+		string_list_append(default_notes_ref(), &display_notes_refs);
+		display_ref_env = getenv(GIT_NOTES_DISPLAY_REF_ENVIRONMENT);
+		if (display_ref_env) {
+			string_list_add_refs_from_colon_sep(&display_notes_refs,
+							    display_ref_env);
+			load_config_refs = 0;
+		} else
+			load_config_refs = 1;
+	}
+
+	git_config(notes_display_config, &load_config_refs);
+
+	if (opt && opt->extra_notes_refs)
+		for_each_string_list(string_list_add_refs_from_list,
+				     opt->extra_notes_refs,
+				     &display_notes_refs);
+
+	display_notes_trees = load_notes_trees(&display_notes_refs);
+	string_list_clear(&display_notes_refs, 0);
+}
+
 void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *note_sha1, combine_notes_fn combine_notes)
 {
@@ -1016,8 +1152,18 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	if (flags & NOTES_SHOW_HEADER)
-		strbuf_addstr(sb, "\nNotes:\n");
+	if (flags & NOTES_SHOW_HEADER) {
+		const char *ref = t->ref;
+		if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) {
+			strbuf_addstr(sb, "\nNotes:\n");
+		} else {
+			if (!prefixcmp(ref, "refs/"))
+				ref += 5;
+			if (!prefixcmp(ref, "notes/"))
+				ref += 6;
+			strbuf_addf(sb, "\nNotes (%s):\n", ref);
+		}
+	}
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
 		linelen = strchrnul(msg_p, '\n') - msg_p;
@@ -1030,3 +1176,13 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 
 	free(msg);
 }
+
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags)
+{
+	int i;
+	assert(display_notes_trees);
+	for (i = 0; display_notes_trees[i]; i++)
+		format_note(display_notes_trees[i], object_sha1, sb,
+			    output_encoding, flags);
+}
diff --git a/notes.h b/notes.h
index bad03cc..69e995d 100644
--- a/notes.h
+++ b/notes.h
@@ -198,4 +198,60 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn,
 void format_note(struct notes_tree *t, const unsigned char *object_sha1,
 		struct strbuf *sb, const char *output_encoding, int flags);
 
+
+struct string_list;
+
+struct display_notes_opt
+{
+	int suppress_default_notes : 1;
+	struct string_list *extra_notes_refs;
+};
+
+/*
+ * Load the notes machinery for displaying several notes trees.
+ *
+ * If 'opt' is not NULL, then it specifies additional settings for the
+ * displaying:
+ *
+ * - suppress_default_notes indicates that the notes from
+ *   core.notesRef and notes.displayRef should not be loaded.
+ *
+ * - extra_notes_refs may contain a list of globs (in the same style
+ *   as notes.displayRef) where notes should be loaded from.
+ */
+void init_display_notes(struct display_notes_opt *opt);
+
+/*
+ * Append notes for the given 'object_sha1' from all trees set up by
+ * init_display_notes() to 'sb'.  The 'flags' are a bitwise
+ * combination of
+ *
+ * - NOTES_SHOW_HEADER: add a 'Notes (refname):' header
+ *
+ * - NOTES_INDENT: indent the notes by 4 places
+ *
+ * You *must* call init_display_notes() before using this function.
+ */
+void format_display_notes(const unsigned char *object_sha1,
+			  struct strbuf *sb, const char *output_encoding, int flags);
+
+/*
+ * Load the notes tree from each ref listed in 'refs'.  The output is
+ * an array of notes_tree*, terminated by a NULL.
+ */
+struct notes_tree **load_notes_trees(struct string_list *refs);
+
+/*
+ * Add all refs that match 'glob' to the 'list'.
+ */
+void string_list_add_refs_by_glob(struct string_list *list, const char *glob);
+
+/*
+ * Add all refs from a colon-separated glob list 'globs' to the end of
+ * 'list'.  Empty components are ignored.  This helper is used to
+ * parse GIT_NOTES_DISPLAY_REF style environment variables.
+ */
+void string_list_add_refs_from_colon_sep(struct string_list *list,
+					 const char *globs);
+
 #endif
diff --git a/pretty.c b/pretty.c
index f999485..6ba3da8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -775,7 +775,7 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		}
 		return 0;	/* unknown %g placeholder */
 	case 'N':
-		format_note(NULL, commit->object.sha1, sb,
+		format_display_notes(commit->object.sha1, sb,
 			    git_log_output_encoding ? git_log_output_encoding
 						    : git_commit_encoding, 0);
 		return 1;
@@ -1096,8 +1096,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (context->show_notes)
-		format_note(NULL, commit->object.sha1, sb, encoding,
-			    NOTES_SHOW_HEADER | NOTES_INDENT);
+		format_display_notes(commit->object.sha1, sb, encoding,
+				     NOTES_SHOW_HEADER | NOTES_INDENT);
 
 	free(reencoded);
 }
diff --git a/refs.c b/refs.c
index f3fcbe0..5a860c4 100644
--- a/refs.c
+++ b/refs.c
@@ -695,7 +695,6 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 {
 	struct strbuf real_pattern = STRBUF_INIT;
 	struct ref_filter filter;
-	const char *has_glob_specials;
 	int ret;
 
 	if (!prefix && prefixcmp(pattern, "refs/"))
@@ -704,8 +703,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 		strbuf_addstr(&real_pattern, prefix);
 	strbuf_addstr(&real_pattern, pattern);
 
-	has_glob_specials = strpbrk(pattern, "?*[");
-	if (!has_glob_specials) {
+	if (!has_glob_specials(pattern)) {
 		/* Append implied '/' '*' if not present. */
 		if (real_pattern.buf[real_pattern.len - 1] != '/')
 			strbuf_addch(&real_pattern, '/');
diff --git a/refs.h b/refs.h
index f7648b9..4a18b08 100644
--- a/refs.h
+++ b/refs.h
@@ -28,6 +28,11 @@ struct ref_lock {
 extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *);
 extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *);
 
+static inline const char *has_glob_specials(const char *pattern)
+{
+	return strpbrk(pattern, "?*[");
+}
+
 /* can be used to learn about broken ref and symref */
 extern int for_each_rawref(each_ref_fn, void *);
 
diff --git a/revision.c b/revision.c
index 29721ec..d6e842e 100644
--- a/revision.c
+++ b/revision.c
@@ -12,6 +12,7 @@
 #include "patch-ids.h"
 #include "decorate.h"
 #include "log-tree.h"
+#include "string-list.h"
 
 volatile show_early_output_fn_t show_early_output;
 
@@ -1191,9 +1192,29 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 	} else if (!strcmp(arg, "--show-notes")) {
 		revs->show_notes = 1;
 		revs->show_notes_given = 1;
+	} else if (!prefixcmp(arg, "--show-notes=")) {
+		struct strbuf buf = STRBUF_INIT;
+		revs->show_notes = 1;
+		revs->show_notes_given = 1;
+		if (!revs->notes_opt.extra_notes_refs)
+			revs->notes_opt.extra_notes_refs = xcalloc(1, sizeof(struct string_list));
+		if (!prefixcmp(arg+13, "refs/"))
+			/* happy */;
+		else if (!prefixcmp(arg+13, "notes/"))
+			strbuf_addstr(&buf, "refs/");
+		else
+			strbuf_addstr(&buf, "refs/notes/");
+		strbuf_addstr(&buf, arg+13);
+		string_list_append(strbuf_detach(&buf, NULL),
+				   revs->notes_opt.extra_notes_refs);
 	} else if (!strcmp(arg, "--no-notes")) {
 		revs->show_notes = 0;
 		revs->show_notes_given = 1;
+	} else if (!strcmp(arg, "--standard-notes")) {
+		revs->show_notes_given = 1;
+		revs->notes_opt.suppress_default_notes = 0;
+	} else if (!strcmp(arg, "--no-standard-notes")) {
+		revs->notes_opt.suppress_default_notes = 1;
 	} else if (!strcmp(arg, "--oneline")) {
 		revs->verbose_header = 1;
 		get_commit_format("oneline", revs);
diff --git a/revision.h b/revision.h
index a14deef..580f6ec 100644
--- a/revision.h
+++ b/revision.h
@@ -3,6 +3,7 @@
 
 #include "parse-options.h"
 #include "grep.h"
+#include "notes.h"
 
 #define SEEN		(1u<<0)
 #define UNINTERESTING   (1u<<1)
@@ -20,6 +21,7 @@
 
 struct rev_info;
 struct log_info;
+struct string_list;
 
 struct rev_info {
 	/* Starting list */
@@ -126,6 +128,9 @@ struct rev_info {
 	struct reflog_walk_info *reflog_info;
 	struct decoration children;
 	struct decoration merge_simplification;
+
+	/* notes-specific options: which refs to show */
+	struct display_notes_opt notes_opt;
 };
 
 #define REV_TREE_SAME		0
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 37b9687..f6cdb33 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -416,7 +416,7 @@ Date:   Thu Apr 7 15:18:13 2005 -0700
 
     6th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -449,7 +449,139 @@ test_expect_success 'Do not show note when core.notesRef is overridden' '
 	test_cmp expect-not-other output
 '
 
+cat > expect-both << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+
+Notes (other):
+    other note
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+
+Notes:
+    replacement for deleted note
+EOF
+
+test_expect_success 'Show all notes when notes.displayRef=refs/notes/*' '
+	GIT_NOTES_REF=refs/notes/commits git notes add \
+		-m"replacement for deleted note" HEAD^ &&
+	GIT_NOTES_REF=refs/notes/commits git notes add -m"order test" &&
+	git config --unset core.notesRef &&
+	git config notes.displayRef "refs/notes/*" &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'core.notesRef is implicitly in notes.displayRef' '
+	git config core.notesRef refs/notes/commits &&
+	git config notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success 'notes.displayRef can be given more than once' '
+	git config --unset core.notesRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git config --add notes.displayRef refs/notes/other &&
+	git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-both-reversed << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes (other):
+    other note
+
+Notes:
+    order test
+EOF
+
+test_expect_success 'notes.displayRef respects order' '
+	git config core.notesRef refs/notes/other &&
+	git config --unset-all notes.displayRef &&
+	git config notes.displayRef refs/notes/commits &&
+	git log -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF works' '
+	git config --unset-all core.notesRef &&
+	git config --unset-all notes.displayRef &&
+	GIT_NOTES_DISPLAY_REF=refs/notes/commits:refs/notes/other \
+		git log -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-none << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+commit bd1753200303d0a0344be813e504253b3d98e74d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:17:13 2005 -0700
+
+    5th
+EOF
+
+test_expect_success 'GIT_NOTES_DISPLAY_REF overrides config' '
+	git config notes.displayRef "refs/notes/*" &&
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log -2 > output &&
+	test_cmp expect-none output
+'
+
+test_expect_success '--show-notes=* adds to GIT_NOTES_DISPLAY_REF' '
+	GIT_NOTES_REF= GIT_NOTES_DISPLAY_REF= git log --show-notes=* -2 > output &&
+	test_cmp expect-both output
+'
+
+cat > expect-commits << EOF
+commit 387a89921c73d7ed72cd94d179c1c7048ca47756
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:18:13 2005 -0700
+
+    6th
+
+Notes:
+    order test
+EOF
+
+test_expect_success '--no-standard-notes' '
+	git log --no-standard-notes --show-notes=commits -1 > output &&
+	test_cmp expect-commits output
+'
+
+test_expect_success '--standard-notes' '
+	git log --no-standard-notes --show-notes=commits \
+		--standard-notes -2 > output &&
+	test_cmp expect-both output
+'
+
+test_expect_success '--show-notes=ref accumulates' '
+	git log --show-notes=other --show-notes=commits \
+		 --no-standard-notes -1 > output &&
+	test_cmp expect-both-reversed output
+'
+
 test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' '
+	git config core.notesRef refs/notes/other &&
 	echo "Note on a tree" > expect
 	git notes add -m "Note on a tree" HEAD: &&
 	git notes show HEAD: > actual &&
@@ -473,7 +605,7 @@ Date:   Thu Apr 7 15:19:13 2005 -0700
 
     7th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -504,7 +636,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 EOF
 
@@ -534,7 +666,7 @@ Date:   Thu Apr 7 15:21:13 2005 -0700
 
     9th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
@@ -553,7 +685,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 EOF
 
@@ -570,7 +702,7 @@ Date:   Thu Apr 7 15:22:13 2005 -0700
 
     10th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -589,7 +721,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     other note
 $whitespace
     yet another note
@@ -620,7 +752,7 @@ Date:   Thu Apr 7 15:23:13 2005 -0700
 
     11th
 
-Notes:
+Notes (other):
     yet another note
 $whitespace
     yet another note
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 400bc6b..3d026b4 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -55,6 +55,7 @@ unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
+unset GIT_NOTES_DISPLAY_REF
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 03/13] Documentation: document post-rewrite hook
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 02/13] Support showing notes from more than one notes tree Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 04/13] commit --amend: invoke " Thomas Rast
                                     ` (9 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

This defines the behaviour of the post-rewrite hook support, which
will be implemented in the following patches.

We deliberately do not document how often the hook will be invoked per
rewriting command, but the interface is designed to keep that at
"once".  This would currently not matter too much, since both rebase
and filter-branch are shellscripts and spawn many processes anyway.
However, when a fast sequencer in C is implemented, it will be
beneficial to only have to run the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 Documentation/githooks.txt |   34 ++++++++++++++++++++++++++++++++++
 1 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 87e2c03..a741769 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -317,6 +317,40 @@ This hook is invoked by 'git gc --auto'. It takes no parameter, and
 exiting with non-zero status from this script causes the 'git gc --auto'
 to abort.
 
+post-rewrite
+~~~~~~~~~~~~
+
+This hook is invoked by commands that rewrite commits (`git commit
+--amend`, 'git-rebase'; currently 'git-filter-branch' does 'not' call
+it!).  Its first argument denotes the command it was invoked by:
+currently one of `amend` or `rebase`.  Further command-dependent
+arguments may be passed in the future.
+
+The hook receives a list of the rewritten commits on stdin, in the
+format
+
+  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
+
+The 'extra-info' is again command-dependent.  If it is empty, the
+preceding SP is also omitted.  Currently, no commands pass any
+'extra-info'.
+
+The following command-specific comments apply:
+
+rebase::
+	For the 'squash' and 'fixup' operation, all commits that were
+	squashed are listed as being rewritten to the squashed commit.
+	This means that there will be several lines sharing the same
+	'new-sha1'.
++
+The commits are guaranteed to be listed in the order that they were
+processed by rebase.
+
+There is no default 'post-rewrite' hook, but see the
+`post-receive-copy-notes` script in `contrib/hooks` for an example
+that copies your git-notes to the rewritten commits.
+
+
 GIT
 ---
 Part of the linkgit:git[1] suite
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 04/13] commit --amend: invoke post-rewrite hook
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (2 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 03/13] Documentation: document post-rewrite hook Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 05/13] rebase: " Thomas Rast
                                     ` (8 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

The rough structure of run_rewrite_hook() comes from
run_receive_hook() in receive-pack.

We introduce a --no-post-rewrite option and use it to avoid the hook
when called from git-rebase -i 'edit'.  The next patch will add full
support in git-rebase, and we only want to invoke the hook once.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin/commit.c             |   39 +++++++++++++++++++++++++++++++
 git-rebase--interactive.sh   |    2 +-
 t/t5407-post-rewrite-hook.sh |   52 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletions(-)
 create mode 100755 t/t5407-post-rewrite-hook.sh

diff --git a/builtin/commit.c b/builtin/commit.c
index f4c7344..0367412 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -66,6 +66,7 @@
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -137,6 +138,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 	OPT_BOOLEAN('z', "null", &null_termination,
 		    "terminate entries with NUL"),
 	OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+	OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 	{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	/* end commit contents options */
@@ -1160,6 +1162,40 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 	return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+			    const unsigned char *newsha1)
+{
+	/* oldsha1 SP newsha1 LF NUL */
+	static char buf[2*40 + 3];
+	struct child_process proc;
+	const char *argv[3];
+	int code;
+	size_t n;
+
+	if (access(git_path(post_rewrite_hook), X_OK) < 0)
+		return 0;
+
+	argv[0] = git_path(post_rewrite_hook);
+	argv[1] = "amend";
+	argv[2] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+
+	code = start_command(&proc);
+	if (code)
+		return code;
+	n = snprintf(buf, sizeof(buf), "%s %s\n",
+		     sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+	write_in_full(proc.in, buf, n);
+	close(proc.in);
+	return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -1303,6 +1339,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
+	if (amend && !no_post_rewrite) {
+		run_rewrite_hook(head_sha1, commit_sha1);
+	}
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 92d19f5..c38efc4 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -436,7 +436,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
-		git commit --amend
+		git commit --amend --no-post-rewrite
 		;;
 	edit|e)
 		comment_for_reflog edit
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
new file mode 100755
index 0000000..1020af9
--- /dev/null
+++ b/t/t5407-post-rewrite-hook.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Thomas Rast
+#
+
+test_description='Test the post-rewrite hook.'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit A foo A &&
+	test_commit B foo B &&
+	test_commit C foo C &&
+	test_commit D foo D
+'
+
+mkdir .git/hooks
+
+cat >.git/hooks/post-rewrite <<EOF
+#!/bin/sh
+echo \$@ > "$TRASH_DIRECTORY"/post-rewrite.args
+cat > "$TRASH_DIRECTORY"/post-rewrite.data
+EOF
+chmod u+x .git/hooks/post-rewrite
+
+clear_hook_input () {
+	rm -f post-rewrite.args post-rewrite.data
+}
+
+verify_hook_input () {
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.args expected.args &&
+	test_cmp "$TRASH_DIRECTORY"/post-rewrite.data expected.data
+}
+
+test_expect_success 'git commit --amend' '
+	clear_hook_input &&
+	echo "D new message" > newmsg &&
+	oldsha=$(git rev-parse HEAD^0) &&
+	git commit -Fnewmsg --amend &&
+	echo amend > expected.args &&
+	echo $oldsha $(git rev-parse HEAD^0) > expected.data &&
+	verify_hook_input
+'
+
+test_expect_success 'git commit --amend --no-post-rewrite' '
+	clear_hook_input &&
+	echo "D new message again" > newmsg &&
+	git commit --no-post-rewrite -Fnewmsg --amend &&
+	test ! -f post-rewrite.args &&
+	test ! -f post-rewrite.data
+'
+
+test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 05/13] rebase: invoke post-rewrite hook
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (3 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 04/13] commit --amend: invoke " Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 06/13] rebase -i: " Thomas Rast
                                     ` (7 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

We have to deal with two separate code paths: a normal rebase, which
actually goes through git-am; and rebase {-m|-s}.

The only small issue with both is that they need to remember the
original sha1 across a possible conflict resolution.  rebase -m
already puts this information in $dotest/current, and we just
introduce a similar file for git-am.

Note that in git-am, the hook really only runs when coming from
git-rebase: the code path that sets the $dotest/original-commit file
is guarded by a test for $dotest/rebasing.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                    |   10 ++++++++++
 git-rebase.sh                |    5 +++++
 t/t5407-post-rewrite-hook.sh |   30 ++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 0 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 50a292a..9b73e0b 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -593,6 +593,7 @@ do
 			echo "Patch is empty.  Was it split wrong?"
 			stop_here $this
 		}
+		rm -f "$dotest/original-commit"
 		if test -f "$dotest/rebasing" &&
 			commit=$(sed -e 's/^From \([0-9a-f]*\) .*/\1/' \
 				-e q "$dotest/$msgnum") &&
@@ -600,6 +601,7 @@ do
 		then
 			git cat-file commit "$commit" |
 			sed -e '1,/^$/d' >"$dotest/msg-clean"
+			echo "$commit" > "$dotest/original-commit"
 		else
 			{
 				sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
@@ -783,6 +785,10 @@ do
 	git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
 	stop_here $this
 
+	if test -f "$dotest/original-commit"; then
+		echo "$(cat "$dotest/original-commit") $commit" >> "$dotest/rewritten"
+	fi
+
 	if test -x "$GIT_DIR"/hooks/post-applypatch
 	then
 		"$GIT_DIR"/hooks/post-applypatch
@@ -791,5 +797,9 @@ do
 	go_next
 done
 
+if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+fi
+
 rm -fr "$dotest"
 git gc --auto
diff --git a/git-rebase.sh b/git-rebase.sh
index fb4fef7..52f8b9b 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -79,6 +79,7 @@ continue_merge () {
 		then
 			printf "Committed: %0${prec}d " $msgnum
 		fi
+		echo "$cmt $(git rev-parse HEAD^0)" >> "$dotest/rewritten"
 	else
 		if test -z "$GIT_QUIET"
 		then
@@ -151,6 +152,10 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$dotest"/rewritten; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+	fi
 	rm -r "$dotest"
 	say All done.
 }
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1020af9..1ecaa4b 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -49,4 +49,34 @@ test_expect_success 'git commit --amend --no-post-rewrite' '
 	test ! -f post-rewrite.data
 '
 
+test_expect_success 'git rebase' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 06/13] rebase -i: invoke post-rewrite hook
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (4 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 05/13] rebase: " Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
                                     ` (6 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Aside from the same issue that rebase also has (remembering the
original commit across a conflict resolution), rebase -i brings an
extra twist: We need to defer writing the rewritten list in the case
of {squash,fixup} because their rewritten result should be the last
commit in the squashed group.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-rebase--interactive.sh   |   46 +++++++++++++++++++-
 t/t5407-post-rewrite-hook.sh |  101 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+), 1 deletions(-)

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index c38efc4..f18a118 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
 # command is processed, this file is deleted.
 AMEND="$DOTEST"/amend
 
+# For the post-rewrite hook, we make a list of rewritten commits and
+# their new sha1s.  The rewritten-pending list keeps the sha1s of
+# commits that have been processed, but not committed yet,
+# e.g. because they are waiting for a 'squash' command.
+REWRITTEN_LIST="$DOTEST"/rewritten-list
+REWRITTEN_PENDING="$DOTEST"/rewritten-pending
+
 PRESERVE_MERGES=
 STRATEGY=
 ONTO=
@@ -198,6 +205,7 @@ make_patch () {
 }
 
 die_with_patch () {
+	echo "$1" > "$DOTEST"/stopped-sha
 	make_patch "$1"
 	git rerere
 	die "$2"
@@ -339,6 +347,7 @@ pick_one_preserving_merges () {
 				printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
 				die_with_patch $sha1 "Error redoing merge $sha1"
 			fi
+			echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
 			;;
 		*)
 			output git cherry-pick "$@" ||
@@ -416,6 +425,26 @@ die_failed_squash() {
 	die_with_patch $1 ""
 }
 
+flush_rewritten_pending() {
+	test -s "$REWRITTEN_PENDING" || return
+	newsha1="$(git rev-parse HEAD^0)"
+	sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
+	rm -f "$REWRITTEN_PENDING"
+}
+
+record_in_rewritten() {
+	oldsha1="$(git rev-parse $1)"
+	echo "$oldsha1" >> "$REWRITTEN_PENDING"
+
+	case "$(peek_next_command)" in
+	    squash|s|fixup|f)
+		;;
+	    *)
+		flush_rewritten_pending
+		;;
+	esac
+}
+
 do_next () {
 	rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
 	read command sha1 rest < "$TODO"
@@ -429,6 +458,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		record_in_rewritten $sha1
 		;;
 	reword|r)
 		comment_for_reflog reword
@@ -437,6 +467,7 @@ do_next () {
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
 		git commit --amend --no-post-rewrite
+		record_in_rewritten $sha1
 		;;
 	edit|e)
 		comment_for_reflog edit
@@ -444,6 +475,7 @@ do_next () {
 		mark_action_done
 		pick_one $sha1 ||
 			die_with_patch $sha1 "Could not apply $sha1... $rest"
+		echo "$1" > "$DOTEST"/stopped-sha
 		make_patch $sha1
 		git rev-parse --verify HEAD > "$AMEND"
 		warn "Stopped at $sha1... $rest"
@@ -500,6 +532,7 @@ do_next () {
 			rm -f "$SQUASH_MSG" "$FIXUP_MSG"
 			;;
 		esac
+		record_in_rewritten $sha1
 		;;
 	*)
 		warn "Unknown command: $command $sha1 $rest"
@@ -528,6 +561,11 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	if test -x "$GIT_DIR"/hooks/post-rewrite &&
+		test -s "$REWRITTEN_LIST"; then
+		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
+		true # we don't care if this hook failed
+	fi &&
 	rm -rf "$DOTEST" &&
 	git gc --auto &&
 	warn "Successfully rebased and updated $HEADNAME."
@@ -562,7 +600,12 @@ skip_unnecessary_picks () {
 		esac
 		echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
 	done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
-	mv -f "$TODO".new "$TODO" ||
+	mv -f "$TODO".new "$TODO" &&
+	case "$(peek_next_command)" in
+	squash|s|fixup|f)
+		record_in_rewritten "$ONTO"
+		;;
+	esac ||
 	die "Could not skip unnecessary pick commands"
 }
 
@@ -676,6 +719,7 @@ first and then run 'git rebase --continue' again."
 				test -n "$amend" && git reset --soft $amend
 				die "Could not commit staged changes."
 			}
+			record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
 		fi
 
 		require_clean_work_tree
diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
index 1ecaa4b..f0f91f1 100755
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
@@ -79,4 +79,105 @@ EOF
 	verify_hook_input
 '
 
+test_expect_success 'git rebase -m' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase -m --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -m --skip' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_must_fail git rebase --onto A B &&
+	test_must_fail git rebase --skip &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+set_fake_editor
+
+# Helper to work around the lack of one-shot exporting for
+# test_must_fail (as it is a shell function)
+test_fail_interactive_rebase () {
+	(
+		FAKE_LINES="$1" &&
+		shift &&
+		export FAKE_LINES &&
+		test_must_fail git rebase -i "$@"
+	)
+}
+
+test_expect_success 'git rebase -i (unchanged)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "1 2" --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD^)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (skip)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "2" --onto A B &&
+	echo D > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (squash)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	test_fail_interactive_rebase "1 squash 2" --onto A B &&
+	echo C > foo &&
+	git add foo &&
+	git rebase --continue &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
+test_expect_success 'git rebase -i (fixup without conflict)' '
+	git reset --hard D &&
+	clear_hook_input &&
+	FAKE_LINES="1 fixup 2" git rebase -i B &&
+	echo rebase >expected.args &&
+	cat >expected.data <<EOF &&
+$(git rev-parse C) $(git rev-parse HEAD)
+$(git rev-parse D) $(git rev-parse HEAD)
+EOF
+	verify_hook_input
+'
+
 test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 07/13] notes: implement 'git notes copy --stdin'
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (5 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 06/13] rebase -i: " Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-06-14 23:40                     ` [PATCH] notes: Initialize variable to appease Sun Studio Ævar Arnfjörð Bjarmason
  2010-03-12 17:04                   ` [PATCH v7 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
                                     ` (5 subsequent siblings)
  12 siblings, 1 reply; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

This implements a mass-copy command that takes a sequence of lines in
the format

  <from-sha1> SP <to-sha1> [ SP <rest> ] LF

on stdin, and copies each <from-sha1>'s notes to the <to-sha1>.  The
<rest> is ignored.  The intent, of course, is that this can read the
same input that the 'post-rewrite' hook gets.

The copy_note() function is exposed for everyone's and in particular
the next commit's use.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 Documentation/git-notes.txt |   12 ++++++++-
 builtin/notes.c             |   56 ++++++++++++++++++++++++++++++++++++++++++-
 notes.c                     |   18 +++++++++++++
 notes.h                     |    9 +++++++
 t/t3301-notes.sh            |   34 ++++++++++++++++++++++++++
 5 files changed, 127 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 95b4bca..064758b 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] <from-object> <to-object>
+'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
 'git notes' append [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [<object>]
 'git notes' show [<object>]
@@ -56,6 +56,16 @@ copy::
 	object has none (use -f to overwrite existing notes to the
 	second object). This subcommand is equivalent to:
 	`git notes add [-f] -C $(git notes list <from-object>) <to-object>`
++
+In `\--stdin` mode, take lines in the format
++
+----------
+<from-object> SP <to-object> [ SP <rest> ] LF
+----------
++
+on standard input, and copy the notes from each <from-object> to its
+corresponding <to-object>.  (The optional `<rest>` is ignored so that
+the command can read the input given to the `post-rewrite` hook.)
 
 append::
 	Append to the notes of an existing object (defaults to HEAD).
diff --git a/builtin/notes.c b/builtin/notes.c
index feb710a..576a989 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -269,6 +269,46 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
+int notes_copy_from_stdin(int force)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct notes_tree *t;
+	int ret = 0;
+
+	init_notes(NULL, NULL, NULL, 0);
+	t = &default_notes_tree;
+
+	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+		unsigned char from_obj[20], to_obj[20];
+		struct strbuf **split;
+		int err;
+
+		split = strbuf_split(&buf, ' ');
+		if (!split[0] || !split[1])
+			die("Malformed input line: '%s'.", buf.buf);
+		strbuf_rtrim(split[0]);
+		strbuf_rtrim(split[1]);
+		if (get_sha1(split[0]->buf, from_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[0]->buf);
+		if (get_sha1(split[1]->buf, to_obj))
+			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
+
+		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+
+		if (err) {
+			error("Failed to copy notes from '%s' to '%s'",
+			      split[0]->buf, split[1]->buf);
+			ret = 1;
+		}
+
+		strbuf_list_free(split);
+	}
+
+	commit_notes(t, "Notes added by 'git notes copy'");
+	free_notes(t);
+	return ret;
+}
+
 int cmd_notes(int argc, const char **argv, const char *prefix)
 {
 	struct notes_tree *t;
@@ -278,7 +318,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	char logmsg[100];
 
 	int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0,
-	    remove = 0, prune = 0, force = 0;
+	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	struct option options[] = {
@@ -297,6 +337,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 			parse_reuse_arg},
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
+		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
 		OPT_END()
 	};
 
@@ -345,8 +386,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && from_stdin) {
+		error("cannot use --stdin with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
+
 	if (copy) {
 		const char *from_ref;
+		if (from_stdin) {
+			if (argc > 1) {
+				error("too many parameters");
+				usage_with_options(git_notes_usage, options);
+			} else {
+				return notes_copy_from_stdin(force);
+			}
+		}
 		if (argc < 3) {
 			error("too few parameters");
 			usage_with_options(git_notes_usage, options);
diff --git a/notes.c b/notes.c
index 216ad24..66912a3 100644
--- a/notes.c
+++ b/notes.c
@@ -1186,3 +1186,21 @@ void format_display_notes(const unsigned char *object_sha1,
 		format_note(display_notes_trees[i], object_sha1, sb,
 			    output_encoding, flags);
 }
+
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn)
+{
+	const unsigned char *note = get_note(t, from_obj);
+	const unsigned char *existing_note = get_note(t, to_obj);
+
+	if (!force && existing_note)
+		return 1;
+
+	if (note)
+		add_note(t, to_obj, note, combine_fn);
+	else if (existing_note)
+		add_note(t, to_obj, null_sha1, combine_fn);
+
+	return 0;
+}
diff --git a/notes.h b/notes.h
index 69e995d..6166927 100644
--- a/notes.h
+++ b/notes.h
@@ -100,6 +100,15 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 		const unsigned char *object_sha1);
 
 /*
+ * Copy a note from one object to another in the given notes_tree.
+ *
+ * Fails if the to_obj already has a note unless 'force' is true.
+ */
+int copy_note(struct notes_tree *t,
+	      const unsigned char *from_obj, const unsigned char *to_obj,
+	      int force, combine_notes_fn combine_fn);
+
+/*
  * Flags controlling behaviour of for_each_note()
  *
  * Default behaviour of for_each_note() is to traverse every single note object
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index f6cdb33..29ef0c6 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -777,4 +777,38 @@ test_expect_success 'cannot copy note from object without notes' '
 	test_must_fail git notes copy HEAD^ HEAD
 '
 
+cat > expect << EOF
+commit e5d4fb5698d564ab8c73551538ecaf2b0c666185
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:25:13 2005 -0700
+
+    13th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit 7038787dfe22a14c3867ce816dbba39845359719
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:24:13 2005 -0700
+
+    12th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --stdin' '
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --stdin &&
+	git log -2 > output &&
+	test_cmp expect output &&
+	test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" &&
+	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
+'
+
 test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 08/13] notes: implement helpers needed for note copying during rewrite
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (6 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 09/13] rebase: support automatic notes copying Thomas Rast
                                     ` (4 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Implement helper functions to load the rewriting config, and to
actually copy the notes.  Also document the config.

Secondly, also implement an undocumented --for-rewrite=<cmd> option to
'git notes copy' which is used like --stdin, but also puts the
configuration for <cmd> into effect.  It will be needed to support the
copying in git-rebase.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 Documentation/config.txt    |   30 +++++++
 Documentation/git-notes.txt |    4 +
 Documentation/githooks.txt  |    4 +
 builtin.h                   |   18 ++++
 builtin/notes.c             |  139 +++++++++++++++++++++++++++++--
 cache.h                     |    2 +
 t/t3301-notes.sh            |  195 +++++++++++++++++++++++++++++++++++++++++++
 t/test-lib.sh               |    2 +
 8 files changed, 386 insertions(+), 8 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0d0aa9c..25da59e 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1360,6 +1360,36 @@ The effective value of "core.notesRef" (possibly overridden by
 GIT_NOTES_REF) is also implicitly added to the list of refs to be
 displayed.
 
+notes.rewrite.<command>::
+	When rewriting commits with <command> (currently `amend` or
+	`rebase`) and this variable is set to `true`, git
+	automatically copies your notes from the original to the
+	rewritten commit.  Defaults to `true`, but see
+	"notes.rewriteRef" below.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
+environment variable, which must be a colon separated list of refs or
+globs.
+
+notes.rewriteMode::
+	When copying notes during a rewrite (see the
+	"notes.rewrite.<command>" option), determines what to do if
+	the target commit already has a note.  Must be one of
+	`overwrite`, `concatenate`, or `ignore`.  Defaults to
+	`concatenate`.
++
+This setting can be overridden with the `GIT_NOTES_REWRITE_MODE`
+environment variable.
+
+notes.rewriteRef::
+	When copying notes during a rewrite, specifies the (fully
+	qualified) ref whose notes should be copied.  The ref may be a
+	glob, in which case notes in all matching refs will be copied.
+	You may also specify this configuration several times.
++
+Does not have a default value; you must configure this variable to
+enable note rewriting.
+
 pack.window::
 	The size of the window used by linkgit:git-pack-objects[1] when no
 	window size is given on the command line. Defaults to 10.
diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 064758b..77311bd 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -35,6 +35,10 @@ This command always manipulates the notes specified in "core.notesRef"
 To change which notes are shown by 'git-log', see the
 "notes.displayRef" configuration.
 
+See the description of "notes.rewrite.<command>" in
+linkgit:git-config[1] for a way of carrying your notes across commands
+that rewrite commits.
+
 
 SUBCOMMANDS
 -----------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index a741769..7183aa9 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -335,6 +335,10 @@ The 'extra-info' is again command-dependent.  If it is empty, the
 preceding SP is also omitted.  Currently, no commands pass any
 'extra-info'.
 
+The hook always runs after the automatic note copying (see
+"notes.rewrite.<command>" in linkgit:git-config.txt) has happened, and
+thus has access to these notes.
+
 The following command-specific comments apply:
 
 rebase::
diff --git a/builtin.h b/builtin.h
index cdf9847..8aebe61 100644
--- a/builtin.h
+++ b/builtin.h
@@ -20,6 +20,24 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		const char *author);
 extern int commit_notes(struct notes_tree *t, const char *msg);
+
+struct notes_rewrite_cfg
+{
+	struct notes_tree **trees;
+	const char *cmd;
+	int enabled;
+	combine_notes_fn *combine;
+	struct string_list *refs;
+	int refs_from_env;
+	int mode_from_env;
+};
+
+combine_notes_fn *parse_combine_notes_fn(const char *v);
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd);
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj);
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c);
+
 extern int check_pager_config(const char *cmd);
 
 extern int cmd_add(int argc, const char **argv, const char *prefix);
diff --git a/builtin/notes.c b/builtin/notes.c
index 576a989..6c2297a 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -16,6 +16,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "parse-options.h"
+#include "string-list.h"
 
 static const char * const git_notes_usage[] = {
 	"git notes [list [<object>]]",
@@ -269,14 +270,121 @@ int commit_notes(struct notes_tree *t, const char *msg)
 	return 0;
 }
 
-int notes_copy_from_stdin(int force)
+
+combine_notes_fn *parse_combine_notes_fn(const char *v)
+{
+	if (!strcasecmp(v, "overwrite"))
+		return combine_notes_overwrite;
+	else if (!strcasecmp(v, "ignore"))
+		return combine_notes_ignore;
+	else if (!strcasecmp(v, "concatenate"))
+		return combine_notes_concatenate;
+	else
+		return NULL;
+}
+
+static int notes_rewrite_config(const char *k, const char *v, void *cb)
+{
+	struct notes_rewrite_cfg *c = cb;
+	if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
+		c->enabled = git_config_bool(k, v);
+		return 0;
+	} else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
+		if (!v)
+			config_error_nonbool(k);
+		c->combine = parse_combine_notes_fn(v);
+		if (!c->combine) {
+			error("Bad notes.rewriteMode value: '%s'", v);
+			return 1;
+		}
+		return 0;
+	} else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
+		/* note that a refs/ prefix is implied in the
+		 * underlying for_each_glob_ref */
+		if (!prefixcmp(v, "refs/notes/"))
+			string_list_add_refs_by_glob(c->refs, v);
+		else
+			warning("Refusing to rewrite notes in %s"
+				" (outside of refs/notes/)", v);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
+{
+	struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
+	const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
+	const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
+	c->cmd = cmd;
+	c->enabled = 1;
+	c->combine = combine_notes_concatenate;
+	c->refs = xcalloc(1, sizeof(struct string_list));
+	c->refs->strdup_strings = 1;
+	c->refs_from_env = 0;
+	c->mode_from_env = 0;
+	if (rewrite_mode_env) {
+		c->mode_from_env = 1;
+		c->combine = parse_combine_notes_fn(rewrite_mode_env);
+		if (!c->combine)
+			error("Bad " GIT_NOTES_REWRITE_MODE_ENVIRONMENT
+			      " value: '%s'", rewrite_mode_env);
+	}
+	if (rewrite_refs_env) {
+		c->refs_from_env = 1;
+		string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
+	}
+	git_config(notes_rewrite_config, c);
+	if (!c->enabled || !c->refs->nr) {
+		string_list_clear(c->refs, 0);
+		free(c->refs);
+		free(c);
+		return NULL;
+	}
+	c->trees = load_notes_trees(c->refs);
+	string_list_clear(c->refs, 0);
+	free(c->refs);
+	return c;
+}
+
+int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
+			  const unsigned char *from_obj, const unsigned char *to_obj)
+{
+	int ret = 0;
+	int i;
+	for (i = 0; c->trees[i]; i++)
+		ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
+	return ret;
+}
+
+void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
+{
+	int i;
+	for (i = 0; c->trees[i]; i++) {
+		commit_notes(c->trees[i], "Notes added by 'git notes copy'");
+		free_notes(c->trees[i]);
+	}
+	free(c->trees);
+	free(c);
+}
+
+int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
+	struct notes_rewrite_cfg *c = NULL;
 	struct notes_tree *t;
 	int ret = 0;
 
-	init_notes(NULL, NULL, NULL, 0);
-	t = &default_notes_tree;
+	if (rewrite_cmd) {
+		c = init_copy_notes_for_rewrite(rewrite_cmd);
+		if (!c)
+			return 0;
+	} else {
+		init_notes(NULL, NULL, NULL, 0);
+		t = &default_notes_tree;
+	}
 
 	while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 		unsigned char from_obj[20], to_obj[20];
@@ -293,7 +401,11 @@ int notes_copy_from_stdin(int force)
 		if (get_sha1(split[1]->buf, to_obj))
 			die("Failed to resolve '%s' as a valid ref.", split[1]->buf);
 
-		err = copy_note(t, from_obj, to_obj, force, combine_notes_overwrite);
+		if (rewrite_cmd)
+			err = copy_note_for_rewrite(c, from_obj, to_obj);
+		else
+			err = copy_note(t, from_obj, to_obj, force,
+					combine_notes_overwrite);
 
 		if (err) {
 			error("Failed to copy notes from '%s' to '%s'",
@@ -304,8 +416,12 @@ int notes_copy_from_stdin(int force)
 		strbuf_list_free(split);
 	}
 
-	commit_notes(t, "Notes added by 'git notes copy'");
-	free_notes(t);
+	if (!rewrite_cmd) {
+		commit_notes(t, "Notes added by 'git notes copy'");
+		free_notes(t);
+	} else {
+		finish_copy_notes_for_rewrite(c);
+	}
 	return ret;
 }
 
@@ -321,6 +437,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	    remove = 0, prune = 0, force = 0, from_stdin = 0;
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
+	const char *rewrite_cmd = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes contents options"),
 		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
@@ -338,6 +455,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
+			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
 	};
 
@@ -386,6 +505,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_notes_usage, options);
 	}
 
+	if (!copy && rewrite_cmd) {
+		error("cannot use --for-rewrite with %s subcommand.", argv[0]);
+		usage_with_options(git_notes_usage, options);
+	}
 	if (!copy && from_stdin) {
 		error("cannot use --stdin with %s subcommand.", argv[0]);
 		usage_with_options(git_notes_usage, options);
@@ -393,12 +516,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	if (copy) {
 		const char *from_ref;
-		if (from_stdin) {
+		if (from_stdin || rewrite_cmd) {
 			if (argc > 1) {
 				error("too many parameters");
 				usage_with_options(git_notes_usage, options);
 			} else {
-				return notes_copy_from_stdin(force);
+				return notes_copy_from_stdin(force, rewrite_cmd);
 			}
 		}
 		if (argc < 3) {
diff --git a/cache.h b/cache.h
index b25d180..32c18b1 100644
--- a/cache.h
+++ b/cache.h
@@ -388,6 +388,8 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
+#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
+#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
 
 /*
  * Repository-local GIT_* environment variables
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index 29ef0c6..a4a0b1d 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -811,4 +811,199 @@ test_expect_success 'git notes copy --stdin' '
 	test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)"
 '
 
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (unconfigured)' '
+	test_commit 14th &&
+	test_commit 15th &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    yet another note
+$whitespace
+    yet another note
+
+commit be28d8b4d9951ad940d229ee3b0b9ee3b1ec273d
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:26:13 2005 -0700
+
+    14th
+
+Notes (other):
+    other note
+$whitespace
+    yet another note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (enabled)' '
+	git config notes.rewriteMode overwrite &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \
+	echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (disabled)' '
+	git config notes.rewrite.bar false &&
+	echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=bar &&
+	git log -2 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (overwrite)' '
+	git notes add -f -m"a fresh note" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (ignore)' '
+	git config notes.rewriteMode ignore &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append)' '
+	git notes add -f -m"another fresh note" HEAD^ &&
+	git config notes.rewriteMode concatenate &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    a fresh note
+    another fresh note
+    append 1
+    append 2
+EOF
+
+test_expect_success 'git notes copy --for-rewrite (append two to one)' '
+	git notes add -f -m"append 1" HEAD^ &&
+	git notes add -f -m"append 2" HEAD^^ &&
+	(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);
+	echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'git notes copy --for-rewrite (append empty)' '
+	git notes remove HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 1
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_MODE works' '
+	git notes add -f -m"replacement note 1" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+cat > expect << EOF
+commit 37a0d4cba38afef96ba54a3ea567e6dac575700b
+Author: A U Thor <author@example.com>
+Date:   Thu Apr 7 15:27:13 2005 -0700
+
+    15th
+
+Notes (other):
+    replacement note 2
+EOF
+
+test_expect_success 'GIT_NOTES_REWRITE_REF works' '
+	git config notes.rewriteMode overwrite &&
+	git notes add -f -m"replacement note 2" HEAD^ &&
+	git config --unset-all notes.rewriteRef &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \
+		git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
+
+test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' '
+	git config notes.rewriteRef refs/notes/other &&
+	git notes add -f -m"replacement note 3" HEAD^ &&
+	echo $(git rev-parse HEAD^) $(git rev-parse HEAD) |
+	GIT_NOTES_REWRITE_REF= git notes copy --for-rewrite=foo &&
+	git log -1 > output &&
+	test_cmp expect output
+'
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 3d026b4..c582964 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -56,6 +56,8 @@ unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 unset GIT_NOTES_REF
 unset GIT_NOTES_DISPLAY_REF
+unset GIT_NOTES_REWRITE_REF
+unset GIT_NOTES_REWRITE_MODE
 GIT_MERGE_VERBOSITY=5
 export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 09/13] rebase: support automatic notes copying
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (7 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 10/13] commit --amend: copy notes to the new commit Thomas Rast
                                     ` (3 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Luckily, all the support already happens to be there.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 git-am.sh                     |    5 ++++-
 git-rebase--interactive.sh    |    4 ++++
 git-rebase.sh                 |    1 +
 t/t3400-rebase.sh             |   17 +++++++++++++++++
 t/t3404-rebase-interactive.sh |   24 ++++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletions(-)

diff --git a/git-am.sh b/git-am.sh
index 9b73e0b..1056075 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -797,8 +797,11 @@ do
 	go_next
 done
 
-if test -s "$dotest"/rewritten && test -x "$GIT_DIR"/hooks/post-rewrite; then
+if test -s "$dotest"/rewritten; then
+    git notes copy --for-rewrite=rebase < "$dotest"/rewritten
+    if test -x "$GIT_DIR"/hooks/post-rewrite; then
 	"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
+    fi
 fi
 
 rm -fr "$dotest"
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index f18a118..a57f043 100755
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -561,6 +561,10 @@ do_next () {
 		test ! -f "$DOTEST"/verbose ||
 			git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
 	} &&
+	{
+		git notes copy --for-rewrite=rebase < "$REWRITTEN_LIST" ||
+		true # we don't care if this copying failed
+	} &&
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$REWRITTEN_LIST"; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
diff --git a/git-rebase.sh b/git-rebase.sh
index 52f8b9b..e0eb956 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -152,6 +152,7 @@ move_to_original_branch () {
 
 finish_rb_merge () {
 	move_to_original_branch
+	git notes copy --for-rewrite=rebase < "$dotest"/rewritten
 	if test -x "$GIT_DIR"/hooks/post-rewrite &&
 		test -s "$dotest"/rewritten; then
 		"$GIT_DIR"/hooks/post-rewrite rebase < "$dotest"/rewritten
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 4314ad2..dbf7dfb 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -151,4 +151,21 @@ test_expect_success 'Rebase a commit that sprinkles CRs in' '
 	git diff --exit-code file-with-cr:CR HEAD:CR
 '
 
+test_expect_success 'rebase can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+test_expect_success 'rebase -m can copy notes' '
+	git reset --hard n3 &&
+	git rebase -m --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
 test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4e35137..19668c2 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -553,4 +553,28 @@ test_expect_success 'reword' '
 	git show HEAD~2 | grep "C changed"
 '
 
+test_expect_success 'rebase -i can copy notes' '
+	git config notes.rewrite.rebase true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit n1 &&
+	test_commit n2 &&
+	test_commit n3 &&
+	git notes add -m"a note" n3 &&
+	git rebase --onto n1 n2 &&
+	test "a note" = "$(git notes show HEAD)"
+'
+
+cat >expect <<EOF
+an earlier note
+a note
+EOF
+
+test_expect_success 'rebase -i can copy notes over a fixup' '
+	git reset --hard n3 &&
+	git notes add -m"an earlier note" n2 &&
+	GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
+	git notes show > output &&
+	test_cmp expect output
+'
+
 test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 10/13] commit --amend: copy notes to the new commit
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (8 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 09/13] rebase: support automatic notes copying Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
                                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Teaches 'git commit --amend' to copy notes.  The catch is that this
must also be guarded by --no-post-rewrite, which we use to prevent
--amend from copying notes during a rebase -i 'edit'/'reword'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---


 builtin/commit.c  |    6 ++++++
 t/t7501-commit.sh |   12 ++++++++++++
 2 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 0367412..8dd104e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1340,6 +1340,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	rerere(0);
 	run_hook(get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
+		struct notes_rewrite_cfg *cfg;
+		cfg = init_copy_notes_for_rewrite("amend");
+		if (cfg) {
+			copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+			finish_copy_notes_for_rewrite(cfg);
+		}
 		run_rewrite_hook(head_sha1, commit_sha1);
 	}
 	if (!quiet)
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 7940901..8297cb4 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -425,4 +425,16 @@ test_expect_success 'amend using the message from a commit named with tag' '
 
 '
 
+test_expect_success 'amend can copy notes' '
+
+	git config notes.rewrite.amend true &&
+	git config notes.rewriteRef "refs/notes/*" &&
+	test_commit foo &&
+	git notes add -m"a note" &&
+	test_tick &&
+	git commit --amend -m"new foo" &&
+	test "$(git notes show)" = "a note"
+
+'
+
 test_done
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 11/13] notes: add shorthand --ref to override GIT_NOTES_REF
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (9 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 10/13] commit --amend: copy notes to the new commit Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 12/13] notes: track whether notes_trees were changed at all Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Adds a shorthand option that overrides the GIT_NOTES_REF variable, and
hence determines the notes tree that will be manipulated.  It also
DWIMs a refs/notes/ prefix.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 Documentation/git-notes.txt |    5 +++++
 builtin/notes.c             |   16 ++++++++++++++++
 2 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 77311bd..02a2baf 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -116,6 +116,11 @@ OPTIONS
 	Like '-C', but with '-c' the editor is invoked, so that
 	the user can further edit the note message.
 
+--ref <ref>::
+	Manipulate the notes tree in <ref>.  This overrides both
+	GIT_NOTES_REF and the "core.notesRef" configuration.  The ref
+	is taken to be in `refs/notes/` if it is not qualified.
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
diff --git a/builtin/notes.c b/builtin/notes.c
index 6c2297a..cb30ad0 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -438,6 +438,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 	int given_object = 0, i = 1, retval = 0;
 	struct msg_arg msg = { 0, 0, STRBUF_INIT };
 	const char *rewrite_cmd = NULL;
+	const char *override_notes_ref = NULL;
 	struct option options[] = {
 		OPT_GROUP("Notes contents options"),
 		{ OPTION_CALLBACK, 'm', "message", &msg, "MSG",
@@ -455,6 +456,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 		OPT_GROUP("Other options"),
 		OPT_BOOLEAN('f', "force", &force, "replace existing notes"),
 		OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
+		OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
+			   "use notes from <notes_ref>"),
 		OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
 			   "load rewriting config for <command> (implies --stdin)"),
 		OPT_END()
@@ -464,6 +467,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
 
 	argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0);
 
+	if (override_notes_ref) {
+		struct strbuf sb = STRBUF_INIT;
+		if (!prefixcmp(override_notes_ref, "refs/notes/"))
+			/* we're happy */;
+		else if (!prefixcmp(override_notes_ref, "notes/"))
+			strbuf_addstr(&sb, "refs/");
+		else
+			strbuf_addstr(&sb, "refs/notes/");
+		strbuf_addstr(&sb, override_notes_ref);
+		setenv("GIT_NOTES_REF", sb.buf, 1);
+		strbuf_release(&sb);
+	}
+
 	if (argc && !strcmp(argv[0], "list"))
 		list = 1;
 	else if (argc && !strcmp(argv[0], "add"))
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 12/13] notes: track whether notes_trees were changed at all
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (10 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  2010-03-12 17:04                   ` [PATCH v7 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

Currently, the notes copying is a bit wasteful since it always creates
new trees, even if no notes were copied at all.

Teach add_note() and remove_note() to flag the affected notes tree as
changed ('dirty').  Then teach builtin/notes.c to use this knowledge
and avoid committing trees that weren't changed.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 builtin/notes.c |    2 ++
 notes.c         |    3 +++
 notes.h         |    1 +
 3 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index cb30ad0..4543d11 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -240,6 +240,8 @@ int commit_notes(struct notes_tree *t, const char *msg)
 		t = &default_notes_tree;
 	if (!t->initialized || !t->ref || !*t->ref)
 		die("Cannot commit uninitialized/unreferenced notes tree");
+	if (!t->dirty)
+		return 0; /* don't have to commit an unchanged tree */
 
 	/* Prepare commit message and reflog message */
 	strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
diff --git a/notes.c b/notes.c
index 66912a3..e72ba02 100644
--- a/notes.c
+++ b/notes.c
@@ -933,6 +933,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
 	t->ref = notes_ref ? xstrdup(notes_ref) : NULL;
 	t->combine_notes = combine_notes;
 	t->initialized = 1;
+	t->dirty = 0;
 
 	if (flags & NOTES_INIT_EMPTY || !notes_ref ||
 	    read_ref(notes_ref, object_sha1))
@@ -1012,6 +1013,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1,
 	if (!t)
 		t = &default_notes_tree;
 	assert(t->initialized);
+	t->dirty = 1;
 	if (!combine_notes)
 		combine_notes = t->combine_notes;
 	l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node));
@@ -1027,6 +1029,7 @@ void remove_note(struct notes_tree *t, const unsigned char *object_sha1)
 	if (!t)
 		t = &default_notes_tree;
 	assert(t->initialized);
+	t->dirty = 1;
 	hashcpy(l.key_sha1, object_sha1);
 	hashclr(l.val_sha1);
 	return note_tree_remove(t, t->root, 0, &l);
diff --git a/notes.h b/notes.h
index 6166927..596e0a5 100644
--- a/notes.h
+++ b/notes.h
@@ -40,6 +40,7 @@
 	char *ref;
 	combine_notes_fn *combine_notes;
 	int initialized;
+	int dirty;
 } default_notes_tree;
 
 /*
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH v7 13/13] git-notes(1): add a section about the meaning of history
  2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
                                     ` (11 preceding siblings ...)
  2010-03-12 17:04                   ` [PATCH v7 12/13] notes: track whether notes_trees were changed at all Thomas Rast
@ 2010-03-12 17:04                   ` Thomas Rast
  12 siblings, 0 replies; 135+ messages in thread
From: Thomas Rast @ 2010-03-12 17:04 UTC (permalink / raw)
  To: git; +Cc: Johannes Sixt, Johan Herland, Junio C Hamano

To the displaying code, the only interesting thing about a notes ref
is that it has a tree of the required format.  However, notes actually
have a history since they are recorded as successive commits.

Make a note about the existence of this history in the manpage, but
keep some doors open if we want to change the details.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Acked-by: Johan Herland <johan@herland.net>
---


 Documentation/git-notes.txt |   14 ++++++++++++++
 1 files changed, 14 insertions(+), 0 deletions(-)

diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt
index 02a2baf..4e5113b 100644
--- a/Documentation/git-notes.txt
+++ b/Documentation/git-notes.txt
@@ -121,6 +121,20 @@ OPTIONS
 	GIT_NOTES_REF and the "core.notesRef" configuration.  The ref
 	is taken to be in `refs/notes/` if it is not qualified.
 
+
+NOTES
+-----
+
+Every notes change creates a new commit at the specified notes ref.
+You can therefore inspect the history of the notes by invoking, e.g.,
+`git log -p notes/commits`.
+
+Currently the commit message only records which operation triggered
+the update, and the commit authorship is determined according to the
+usual rules (see linkgit:git-commit[1]).  These details may change in
+the future.
+
+
 Author
 ------
 Written by Johannes Schindelin <johannes.schindelin@gmx.de> and
-- 
1.7.0.2.417.gbc354

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* [PATCH] notes: Initialize variable to appease Sun Studio
  2010-03-12 17:04                   ` [PATCH v7 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
@ 2010-06-14 23:40                     ` Ævar Arnfjörð Bjarmason
  2010-06-19  4:52                       ` Junio C Hamano
  0 siblings, 1 reply; 135+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-14 23:40 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Thomas Rast, Ævar Arnfjörð Bjarmason

Sun Studio 12 Update 1 thinks that *t could be uninitialized,
ostensibly because it doesn't take rewrite_cmd into account in its
static analysis.

    builtin/notes.c: In function `notes_copy_from_stdin':
    builtin/notes.c:419: warning: 't' might be used uninitialized in this function

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/notes.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/builtin/notes.c b/builtin/notes.c
index ba8fd17..648033c 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -416,7 +416,7 @@ int notes_copy_from_stdin(int force, const char *rewrite_cmd)
 {
 	struct strbuf buf = STRBUF_INIT;
 	struct notes_rewrite_cfg *c = NULL;
-	struct notes_tree *t;
+	struct notes_tree *t = NULL;
 	int ret = 0;
 
 	if (rewrite_cmd) {
-- 
1.7.1.251.g92a7

^ permalink raw reply related	[flat|nested] 135+ messages in thread

* Re: [PATCH] notes: Initialize variable to appease Sun Studio
  2010-06-14 23:40                     ` [PATCH] notes: Initialize variable to appease Sun Studio Ævar Arnfjörð Bjarmason
@ 2010-06-19  4:52                       ` Junio C Hamano
  2010-06-19 11:58                         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 135+ messages in thread
From: Junio C Hamano @ 2010-06-19  4:52 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, Thomas Rast

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> Sun Studio 12 Update 1 thinks that *t could be uninitialized,
> ostensibly because it doesn't take rewrite_cmd into account in its
> static analysis.

Hmm, I am wondering if I should apply this or just tell you to fix your
compiler ;-)

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH] notes: Initialize variable to appease Sun Studio
  2010-06-19  4:52                       ` Junio C Hamano
@ 2010-06-19 11:58                         ` Ævar Arnfjörð Bjarmason
  2010-06-21 20:53                           ` Ramsay Jones
  0 siblings, 1 reply; 135+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-19 11:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Thomas Rast

On Sat, Jun 19, 2010 at 04:52, Junio C Hamano <gitster@pobox.com> wrote:
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> Sun Studio 12 Update 1 thinks that *t could be uninitialized,
>> ostensibly because it doesn't take rewrite_cmd into account in its
>> static analysis.
>
> Hmm, I am wondering if I should apply this or just tell you to fix your
> compiler ;-)

I just noticed it when testing on Solaris, I don't use it regularly.

IIRC pretty much exactly the same changes were proposed for
compatibility with HP/UX a while back, and looking through the source
it seems a lot of things initialize struct pointers to NULL. Although
I haven't found any examples yet where they strictly didn't have to.

^ permalink raw reply	[flat|nested] 135+ messages in thread

* Re: [PATCH] notes: Initialize variable to appease Sun Studio
  2010-06-19 11:58                         ` Ævar Arnfjörð Bjarmason
@ 2010-06-21 20:53                           ` Ramsay Jones
  0 siblings, 0 replies; 135+ messages in thread
From: Ramsay Jones @ 2010-06-21 20:53 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Junio C Hamano, git, Thomas Rast

Ævar Arnfjörð Bjarmason wrote:
> On Sat, Jun 19, 2010 at 04:52, Junio C Hamano <gitster@pobox.com> wrote:
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>> Sun Studio 12 Update 1 thinks that *t could be uninitialized,
>>> ostensibly because it doesn't take rewrite_cmd into account in its
>>> static analysis.
>> Hmm, I am wondering if I should apply this or just tell you to fix your
>> compiler ;-)
> 
> I just noticed it when testing on Solaris, I don't use it regularly.

gcc version 3.4.4 issues exactly the same warning (but gcc versions 4.1.2
and 4.4.0 don't).

However, gcc 3.4.4 also issues a warning for notes.c, thus:

    notes.c: In function `write_each_non_note_until':
    notes.c:719: warning: 'cmp' might be used uninitialized in this function

The obvious patch is below, ;-)

ATB,
Ramsay Jones

-- >8 --
From: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Date: Mon, 21 Jun 2010 19:52:29 +0100
Subject: [PATCH] notes: Initialise variable to appease gcc

gcc version 3.4.4 thinks that the 'cmp' variable could be used
while uninitialised and complains thus:

    notes.c: In function `write_each_non_note_until':
    notes.c:719: warning: 'cmp' might be used uninitialized in \
        this function

Note that gcc versions 4.1.2 and 4.4.0 do not issue this warning.

Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
---
 notes.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/notes.c b/notes.c
index 6ee04e7..30d6ded 100644
--- a/notes.c
+++ b/notes.c
@@ -716,7 +716,7 @@ static int write_each_non_note_until(const char *note_path,
 		struct write_each_note_data *d)
 {
 	struct non_note *n = d->next_non_note;
-	int cmp, ret;
+	int cmp = 0, ret;
 	while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) {
 		if (note_path && cmp == 0)
 			; /* do nothing, prefer note to non-note */
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 135+ messages in thread

end of thread, other threads:[~2010-06-22 19:13 UTC | newest]

Thread overview: 135+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-02-14 16:17 [RFC PATCH 0/6] post-rewrite hook and copying notes Thomas Rast
2010-02-14 16:17 ` [RFC PATCH 1/6] Documentation: document post-rewrite hook Thomas Rast
2010-02-14 16:17 ` [RFC PATCH 2/6] commit --amend: invoke " Thomas Rast
2010-02-14 16:17 ` [RFC PATCH 3/6] filter-branch: " Thomas Rast
2010-02-15 20:36   ` Johannes Sixt
2010-02-14 16:17 ` [RFC PATCH 4/6] rebase: " Thomas Rast
2010-02-14 16:17 ` [RFC PATCH 5/6] rebase -i: " Thomas Rast
2010-02-14 16:17 ` [RFC PATCH 6/6] contrib: add a hook that copies notes over rewrites Thomas Rast
2010-02-14 16:21   ` Thomas Rast
2010-02-14 21:46 ` [PATCH] WIP: git notes copy --stdin Thomas Rast
2010-02-15  1:25   ` Johan Herland
2010-02-16 23:25 ` [RFC PATCH v2 00/11] post-rewrite / automatic notes copying Thomas Rast
2010-02-16 23:25   ` [RFC PATCH v2 01/11] Documentation: document post-rewrite hook Thomas Rast
2010-02-16 23:59     ` Junio C Hamano
2010-02-17  0:18       ` Thomas Rast
2010-02-17  0:29         ` Junio C Hamano
2010-02-16 23:25   ` [RFC PATCH v2 02/11] commit --amend: invoke " Thomas Rast
2010-02-16 23:25   ` [RFC PATCH v2 03/11] rebase: " Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 04/11] rebase -i: " Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 05/11] notes: clean up t3301 Thomas Rast
2010-02-16 23:52     ` Junio C Hamano
2010-02-16 23:26   ` [RFC PATCH v2 06/11] notes: implement 'git notes copy --stdin' Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 07/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-02-16 23:58     ` Junio C Hamano
2010-02-17  0:09       ` Thomas Rast
2010-02-17  0:18         ` Junio C Hamano
2010-02-20 14:58           ` [WIP/RFC PATCH] Support showing notes from more than one notes tree Thomas Rast
2010-02-20 15:23             ` Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 08/11] rebase: support automatic notes copying Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 09/11] commit --amend: copy notes to the new commit Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 10/11] filter-branch: invoke post-rewrite hook Thomas Rast
2010-02-16 23:26   ` [RFC PATCH v2 11/11] filter-branch: learn how to filter notes Thomas Rast
2010-02-17 19:59     ` Johannes Sixt
2010-02-17 23:06       ` Thomas Rast
2010-02-20 22:16   ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 01/12] Support showing notes from more than one notes tree Thomas Rast
2010-02-21  3:06       ` Junio C Hamano
2010-02-20 22:16     ` [RFC PATCH v3 02/12] Documentation: document post-rewrite hook Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 03/12] commit --amend: invoke " Thomas Rast
2010-02-21  3:12       ` Junio C Hamano
2010-02-20 22:16     ` [RFC PATCH v3 04/12] rebase: " Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 05/12] rebase -i: " Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 06/12] notes: implement 'git notes copy --stdin' Thomas Rast
2010-02-21  3:31       ` Junio C Hamano
2010-02-20 22:16     ` [RFC PATCH v3 07/12] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-02-21  3:34       ` Junio C Hamano
2010-02-20 22:16     ` [RFC PATCH v3 08/12] rebase: support automatic notes copying Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 09/12] commit --amend: copy notes to the new commit Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 10/12] filter-branch: invoke post-rewrite hook Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 11/12] filter-branch: learn how to filter notes Thomas Rast
2010-02-20 22:16     ` [RFC PATCH v3 12/12] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
2010-02-21  3:47     ` [RFC PATCH v3 00/12] several notes refs, post-rewrite, notes rewriting Junio C Hamano
2010-02-21  6:14       ` Thomas Rast
2010-02-22  0:18         ` Junio C Hamano
2010-02-22  0:10     ` [PATCH v4 00/11] " Thomas Rast
2010-02-22  0:10       ` [PATCH v4 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
2010-02-22  0:10       ` [PATCH v4 02/11] Support showing notes from more than one notes tree Thomas Rast
2010-02-22 23:20         ` Junio C Hamano
2010-02-22 23:25           ` Thomas Rast
2010-02-23  0:21             ` Junio C Hamano
2010-02-22  0:10       ` [PATCH v4 03/11] Documentation: document post-rewrite hook Thomas Rast
2010-02-22  0:10       ` [PATCH v4 04/11] commit --amend: invoke " Thomas Rast
2010-02-22  0:10       ` [PATCH v4 05/11] rebase: " Thomas Rast
2010-02-22  0:10       ` [PATCH v4 06/11] rebase -i: " Thomas Rast
2010-02-22  0:10       ` [PATCH v4 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
2010-02-22  0:10       ` [PATCH v4 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-02-22  0:10       ` [PATCH v4 09/11] rebase: support automatic notes copying Thomas Rast
2010-02-22  0:10       ` [PATCH v4 10/11] commit --amend: copy notes to the new commit Thomas Rast
2010-02-22  0:10       ` [PATCH v4 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
2010-02-22  0:25       ` [PATCH v4 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
2010-02-22  0:32         ` Thomas Rast
2010-02-23  0:42     ` [PATCH v5 " Thomas Rast
2010-02-23  0:42       ` [PATCH v5 01/11] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
2010-02-23  0:42       ` [PATCH v5 02/11] Support showing notes from more than one notes tree Thomas Rast
2010-02-23  1:47         ` Junio C Hamano
2010-02-23 17:10           ` Thomas Rast
2010-02-23 17:34             ` [PATCH] format-patch: learn to fill comment section of email from notes Thomas Rast
2010-02-23 17:34               ` [PATCH] BROKEN -- " Thomas Rast
2010-02-23 17:37                 ` Thomas Rast
2010-02-24  7:45                   ` Stephen Boyd
2010-02-23 21:56               ` [PATCH] " Junio C Hamano
2010-03-10 14:08               ` Thomas Rast
2010-02-23  0:42       ` [PATCH v5 03/11] Documentation: document post-rewrite hook Thomas Rast
2010-02-23  0:42       ` [PATCH v5 04/11] commit --amend: invoke " Thomas Rast
2010-02-23  0:42       ` [PATCH v5 05/11] rebase: " Thomas Rast
2010-02-23  0:42       ` [PATCH v5 06/11] rebase -i: " Thomas Rast
2010-02-24  6:15         ` Junio C Hamano
2010-02-23  0:42       ` [PATCH v5 07/11] notes: implement 'git notes copy --stdin' Thomas Rast
2010-02-23  0:42       ` [PATCH v5 08/11] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-02-23  0:42       ` [PATCH v5 09/11] rebase: support automatic notes copying Thomas Rast
2010-02-25  3:58         ` Junio C Hamano
2010-03-10 14:03           ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Thomas Rast
2010-03-10 14:03             ` [PATCH v6 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
2010-03-11  8:55               ` Johan Herland
2010-03-10 14:03             ` [PATCH v6 02/13] Support showing notes from more than one notes tree Thomas Rast
2010-03-11 10:03               ` Johan Herland
2010-03-12 17:04                 ` [PATCH v7 00/13] tr/display-notes Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 01/13] test-lib: unset GIT_NOTES_REF to stop it from influencing tests Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 02/13] Support showing notes from more than one notes tree Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 03/13] Documentation: document post-rewrite hook Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 04/13] commit --amend: invoke " Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 05/13] rebase: " Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 06/13] rebase -i: " Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
2010-06-14 23:40                     ` [PATCH] notes: Initialize variable to appease Sun Studio Ævar Arnfjörð Bjarmason
2010-06-19  4:52                       ` Junio C Hamano
2010-06-19 11:58                         ` Ævar Arnfjörð Bjarmason
2010-06-21 20:53                           ` Ramsay Jones
2010-03-12 17:04                   ` [PATCH v7 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 09/13] rebase: support automatic notes copying Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 10/13] commit --amend: copy notes to the new commit Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 12/13] notes: track whether notes_trees were changed at all Thomas Rast
2010-03-12 17:04                   ` [PATCH v7 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
2010-03-10 14:03             ` [PATCH v6 03/13] Documentation: document post-rewrite hook Thomas Rast
2010-03-10 14:05             ` [PATCH v6 04/13] commit --amend: invoke " Thomas Rast
2010-03-10 14:05             ` [PATCH v6 05/13] rebase: " Thomas Rast
2010-03-10 14:05             ` [PATCH v6 06/13] rebase -i: " Thomas Rast
2010-03-10 14:05             ` [PATCH v6 07/13] notes: implement 'git notes copy --stdin' Thomas Rast
2010-03-11 10:30               ` Johan Herland
2010-03-10 14:05             ` [PATCH v6 08/13] notes: implement helpers needed for note copying during rewrite Thomas Rast
2010-03-11 10:50               ` Johan Herland
2010-03-10 14:05             ` [PATCH v6 09/13] rebase: support automatic notes copying Thomas Rast
2010-03-10 14:05             ` [PATCH v6 10/13] commit --amend: copy notes to the new commit Thomas Rast
2010-03-10 14:05             ` [PATCH v6 11/13] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
2010-03-11 10:56               ` Johan Herland
2010-03-10 14:05             ` [PATCH v6 12/13] notes: track whether notes_trees were changed at all Thomas Rast
2010-03-11 10:58               ` Johan Herland
2010-03-10 14:06             ` [PATCH v6 13/13] git-notes(1): add a section about the meaning of history Thomas Rast
2010-03-11 10:59               ` Johan Herland
2010-03-10 21:23             ` [PATCH v6 00/13] several notes refs, post-rewrite, notes rewriting Junio C Hamano
2010-02-23  0:42       ` [PATCH v5 10/11] commit --amend: copy notes to the new commit Thomas Rast
2010-02-23  0:42       ` [PATCH v5 11/11] notes: add shorthand --ref to override GIT_NOTES_REF Thomas Rast
2010-02-23  0:49       ` [PATCH v5 00/11] several notes refs, post-rewrite, notes rewriting Junio C Hamano
2010-02-23  0:49       ` Thomas Rast

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.