[RFC/PATCH v2 3/3] Add 'update-branch' hook

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This hook is invoked before a branch is updated, either when a branch is
created or updated with 'git branch', or when it's rebased with 'git
rebase'.  It receives three parameters; the name of the branch, the
SHA-1 of the latest commit, and the SHA-1 of the first commit of the
branch.

When a branch is created the first and last commits of the branch are
the same, however when a branch is rebased they are not. If the SHA-1 of
the first commit of the branch is not available (e.g. git reset) a null
SHA-1 (40 zeroes) would be passed.

The hook exit status can be used to deny the branch update.

It can be used to verify the validity of branch names, and also to keep
track of the origin point of a branch, which is otherwise not possible
to find out [1].

[1] http://thread.gmane.org/gmane.comp.version-control.git/198587

Signed-off-by: Felipe Contreras <felipe.contreras@xxxxxxxxx>
---
 Documentation/githooks.txt    | 15 +++++++++
 branch.c                      | 12 ++++++-
 builtin/clone.c               |  8 +++--
 builtin/reset.c               |  5 +++
 git-rebase--interactive.sh    |  4 +++
 git-rebase.sh                 |  4 +++
 t/t5408-update-branch-hook.sh | 76 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 121 insertions(+), 3 deletions(-)
 create mode 100755 t/t5408-update-branch-hook.sh

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index d954bf6..4a44262 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -381,6 +381,21 @@ rebase::
 The commits are guaranteed to be listed in the order that they were
 processed by rebase.
 
+update-branch
+~~~~~~~~~~~~~
+
+This hook is invoked before a branch is updated, either when a branch is
+created or updated with 'git branch', or when it's rebased with 'git rebase'.
+It receives three parameters; the name of the branch, the SHA-1 of the latest
+commit, and the SHA-1 of the first commit of the branch.
+
+When a branch is created the first and last commits of the branch are the same,
+however when a branch is rebased they are not. If the SHA-1 of the first
+commit of the branch is not available (e.g. git reset) a null SHA-1 (40 zeroes)
+would be passed.
+
+The hook exit status can be used to deny the branch update.
+
 
 GIT
 ---
diff --git a/branch.c b/branch.c
index 660097b..7ec0be5 100644
--- a/branch.c
+++ b/branch.c
@@ -4,6 +4,7 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
+#include "run-command.h"
 
 struct tracking {
 	struct refspec spec;
@@ -234,6 +235,7 @@ void create_branch(const char *head,
 	int forcing = 0;
 	int dont_change_ref = 0;
 	int explicit_tracking = 0;
+	unsigned const char *orig_sha1;
 
 	if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
 		explicit_tracking = 1;
@@ -304,9 +306,17 @@ void create_branch(const char *head,
 	if (real_ref && track)
 		setup_tracking(ref.buf + 11, real_ref, track, quiet);
 
-	if (!dont_change_ref)
+	if (!dont_change_ref) {
+		orig_sha1 = forcing ? null_sha1 : sha1;
+		if (run_hook_le(NULL, "update-branch", ref.buf + 11,
+					sha1_to_hex(sha1), sha1_to_hex(orig_sha1), NULL)) {
+			unlock_ref(lock);
+			die("hook 'update-branch' returned error");
+		}
+
 		if (write_ref_sha1(lock, sha1, msg) < 0)
 			die_errno(_("Failed to write ref"));
+	}
 
 	strbuf_release(&ref);
 	free(real_ref);
diff --git a/builtin/clone.c b/builtin/clone.c
index 9b3c04d..d68c49b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -581,7 +581,7 @@ static void update_remote_refs(const struct ref *refs,
 	}
 }
 
-static void update_head(const struct ref *our, const struct ref *remote,
+static int update_head(const struct ref *our, const struct ref *remote,
 			const char *msg)
 {
 	if (our && starts_with(our->name, "refs/heads/")) {
@@ -589,6 +589,9 @@ static void update_head(const struct ref *our, const struct ref *remote,
 		create_symref("HEAD", our->name, NULL);
 		if (!option_bare) {
 			const char *head = skip_prefix(our->name, "refs/heads/");
+			if (run_hook_le(NULL, "update-branch", head,
+						sha1_to_hex(our->old_sha1), sha1_to_hex(our->old_sha1), NULL))
+				return 1;
 			update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
 			install_branch_config(0, head, option_origin, our->name);
 		}
@@ -606,6 +609,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
 		update_ref(msg, "HEAD", remote->old_sha1,
 			   NULL, REF_NODEREF, DIE_ON_ERR);
 	}
+	return 0;
 }
 
 static int checkout(void)
@@ -987,7 +991,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	update_remote_refs(refs, mapped_refs, remote_head_points_at,
 			   branch_top.buf, reflog_msg.buf, transport, !is_local);
 
-	update_head(our_head_points_at, remote_head, reflog_msg.buf);
+	err = update_head(our_head_points_at, remote_head, reflog_msg.buf);
 
 	transport_unlock_pack(transport);
 	transport_disconnect(transport);
diff --git a/builtin/reset.c b/builtin/reset.c
index f4e0875..9d8cc1f 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -246,11 +246,16 @@ static int reset_refs(const char *rev, const unsigned char *sha1)
 	struct strbuf msg = STRBUF_INIT;
 	unsigned char *orig = NULL, sha1_orig[20],
 		*old_orig = NULL, sha1_old_orig[20];
+	const char *head;
 
 	if (!get_sha1("ORIG_HEAD", sha1_old_orig))
 		old_orig = sha1_old_orig;
 	if (!get_sha1("HEAD", sha1_orig)) {
 		orig = sha1_orig;
+		head = resolve_ref_unsafe("HEAD", sha1_orig, 1, NULL);
+		if (run_hook_le(NULL, "update-branch", skip_prefix(head, "refs/heads/"),
+					sha1_to_hex(sha1), sha1_to_hex(null_sha1), NULL))
+			return 1;
 		set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
 		update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
 	} else if (old_orig)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 1c41cbd..1bedb27 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -628,6 +628,10 @@ do_next () {
 	case $head_name in
 	refs/*)
 		message="$GIT_REFLOG_ACTION: $head_name onto $onto" &&
+		if test -x "$GIT_DIR"/hooks/update-branch; then
+			"$GIT_DIR"/hooks/update-branch $branch_name \
+				$newhead $onto
+		fi &&
 		git update-ref -m "$message" $head_name $newhead $orig_head &&
 		git symbolic-ref \
 		  -m "$GIT_REFLOG_ACTION: returning to $head_name" \
diff --git a/git-rebase.sh b/git-rebase.sh
index 2c75e9f..3ede5be 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -145,6 +145,10 @@ move_to_original_branch () {
 	case "$head_name" in
 	refs/*)
 		message="rebase finished: $head_name onto $onto"
+		if test -x "$GIT_DIR"/hooks/update-branch; then
+			"$GIT_DIR"/hooks/update-branch $branch_name \
+				$(git rev-parse HEAD) $onto
+		fi &&
 		git update-ref -m "$message" \
 			$head_name $(git rev-parse HEAD) $orig_head &&
 		git symbolic-ref \
diff --git a/t/t5408-update-branch-hook.sh b/t/t5408-update-branch-hook.sh
new file mode 100755
index 0000000..8a05aa2
--- /dev/null
+++ b/t/t5408-update-branch-hook.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+test_description='Test the update-branch hook'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	mkdir -p .git/hooks &&
+	cat > .git/hooks/update-branch <<-\EOF &&
+	#!/bin/sh
+	echo $@ > "$GIT_DIR"/update-branch.out
+	EOF
+	chmod +x .git/hooks/update-branch &&
+	echo one > content &&
+	git add content &&
+	git commit -a -m one &&
+	echo two > content &&
+	git commit -a -m two
+'
+
+test_expect_success 'creating a branch' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	git branch new master &&
+	echo "new $(git rev-parse master) $(git rev-parse master)" > expected &&
+	test_cmp expected .git/update-branch.out
+'
+
+test_expect_success 'creating a branch with checkout' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	git checkout -b test master &&
+	echo three > content &&
+	git commit -a -m three &&
+	echo "test $(git rev-parse master) $(git rev-parse master)" > expected &&
+	test_cmp expected .git/update-branch.out
+'
+
+test_expect_success 'reseting a branch' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	git branch --force new master &&
+	echo "new $(git rev-parse master) 0000000000000000000000000000000000000000" > expected &&
+	test_cmp expected .git/update-branch.out
+'
+
+test_expect_success 'reseting a branch with reset' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	git checkout test &&
+	git reset --hard master &&
+	echo "test $(git rev-parse master) 0000000000000000000000000000000000000000" > expected &&
+	test_cmp expected .git/update-branch.out
+'
+
+test_expect_success 'cloning' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	mkdir -p templates/hooks &&
+	cat > templates/hooks/update-branch <<-\EOF &&
+	#!/bin/sh
+	echo $@ > "$GIT_DIR"/update-branch.out
+	EOF
+	chmod +x templates/hooks/update-branch &&
+	git clone --template=templates . clone &&
+	echo "test $(git rev-parse test) $(git rev-parse test)" > expected &&
+	test_cmp expected clone/.git/update-branch.out
+'
+
+test_expect_success 'doing a rebase' '
+	test_when_finished "rm -f .git/update-branch.out" &&
+	git checkout -b next master &&
+	echo three > content &&
+	git commit -a -m three &&
+	git branch test-old test &
+	git rebase -f --onto next master test &&
+	echo "test $(git rev-parse HEAD) $(git rev-parse next)" > expected &&
+	test_cmp expected .git/update-branch.out
+'
+
+test_done
-- 
1.9.2+fc1.1.g5c924db

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]