[PATCH v3] Allow update hooks to update refs on their own

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

 



This is useful in cases where a hook needs to modify an incoming commit
in some way, e.g., adding an annotation to the commit message, noting
the location of output from a profiling tool, or committing to an svn
repository using git-svn.

recieve-pack will now send the post-update SHA1 back to send-pack. If
it's different than the one that was pushed, git-push won't do the
fake update of the local tracking ref and will instead inform the user
that the tracking ref needs to be fetched.

Signed-off-by: Steven Grimm <koreth@xxxxxxxxxxxxx>
---

	This is on top of "next". It doesn't do any automatic fetching,
	just prints a status message.

 Documentation/git-receive-pack.txt |    8 +++-
 builtin-send-pack.c                |   65 +++++++++++++++++++++++++++-------
 cache.h                            |    1 +
 receive-pack.c                     |   68 +++++++++++++++++++++++-------------
 4 files changed, 103 insertions(+), 39 deletions(-)

diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 2633d94..115ae97 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -74,8 +74,12 @@ Note that the hook is called before the refname is updated,
 so either sha1-old is 0\{40} (meaning there is no such ref yet),
 or it should match what is recorded in refname.
 
-The hook should exit with non-zero status if it wants to disallow
-updating the named ref.  Otherwise it should exit with zero.
+The hook may optionally choose to update the ref on its own, e.g.,
+if it needs to modify incoming revisions in some way. If it updates
+the ref, it should exit with a status of 100.  The hook should exit
+with a status between 1 and 99 if it wants to disallow updating the
+named ref.  Otherwise it should exit with zero, and the ref will be
+updated automatically.
 
 Successful execution (a zero exit status) of this hook does not
 ensure the ref will actually be updated, it is only a prerequisite.
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 25ae1fe..4ba716a 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -164,7 +164,9 @@ static int receive_status(int in, struct ref *refs)
 	hint = NULL;
 	while (1) {
 		char *refname;
+		char *newsha1_hex;
 		char *msg;
+		unsigned char newsha1[20];
 		len = packet_read_line(in, line, sizeof(line));
 		if (!len)
 			break;
@@ -177,7 +179,16 @@ static int receive_status(int in, struct ref *refs)
 
 		line[strlen(line)-1] = '\0';
 		refname = line + 3;
-		msg = strchr(refname, ' ');
+		newsha1_hex = strchr(refname, ' ');
+		if (newsha1_hex) {
+			*newsha1_hex++ = '\0';
+			if (get_sha1_hex(newsha1_hex, newsha1)) {
+				fprintf(stderr, "protocol error: bad sha1 %s\n",
+					newsha1_hex);
+				newsha1_hex = NULL;
+			}
+		}
+		msg = strchr(newsha1_hex, ' ');
 		if (msg)
 			*msg++ = '\0';
 
@@ -197,8 +208,16 @@ static int receive_status(int in, struct ref *refs)
 			continue;
 		}
 
-		if (line[0] == 'o' && line[1] == 'k')
-			hint->status = REF_STATUS_OK;
+		if (line[0] == 'o' && line[1] == 'k') {
+			if (newsha1_hex != NULL &&
+			    hashcmp(hint->new_sha1, newsha1)) {
+				hint->status = REF_STATUS_REMOTE_CHANGED;
+				hashcpy(hint->new_sha1, newsha1);
+			}
+			else {
+				hint->status = REF_STATUS_OK;
+			}
+		}
 		else {
 			hint->status = REF_STATUS_REMOTE_REJECT;
 			ret = -1;
@@ -215,21 +234,34 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
 {
 	struct refspec rs;
 
-	if (ref->status != REF_STATUS_OK)
+	if (ref->status != REF_STATUS_OK &&
+	    ref->status != REF_STATUS_REMOTE_CHANGED)
 		return;
 
 	rs.src = ref->name;
 	rs.dst = NULL;
 
 	if (!remote_find_tracking(remote, &rs)) {
-		if (args.verbose)
-			fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
-		if (ref->deletion) {
-			if (delete_ref(rs.dst, NULL))
-				error("Failed to delete");
-		} else
-			update_ref("update by push", rs.dst,
-					ref->new_sha1, NULL, 0, 0);
+		if (ref->status == REF_STATUS_REMOTE_CHANGED) {
+			if (args.verbose) {
+				fprintf(stderr, "local tracking ref '%s' "
+					"needs to be fetched\n", rs.dst);
+			}
+		}
+		else {
+			if (args.verbose) {
+				fprintf(stderr, "updating local tracking "
+					"ref '%s'\n", rs.dst);
+			}
+			if (ref->deletion) {
+				if (delete_ref(rs.dst, NULL))
+					error("Failed to delete");
+			}
+			else {
+				update_ref("update by push", rs.dst,
+						ref->new_sha1, NULL, 0, 0);
+			}
+		}
 		free(rs.dst);
 	}
 }
@@ -329,6 +361,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count)
 				ref->deletion ? NULL : ref->peer_ref,
 				"remote failed to report status");
 		break;
+	case REF_STATUS_REMOTE_CHANGED:
+		print_ref_status('+', "[changed]", ref, ref->peer_ref,
+				"remote made changes to revisions");
+		break;
 	case REF_STATUS_OK:
 		print_ok_ref_status(ref);
 		break;
@@ -349,12 +385,14 @@ static void print_push_status(const char *dest, struct ref *refs)
 	}
 
 	for (ref = refs; ref; ref = ref->next)
-		if (ref->status == REF_STATUS_OK)
+		if (ref->status == REF_STATUS_OK ||
+		    ref->status == REF_STATUS_REMOTE_CHANGED)
 			n += print_one_push_status(ref, dest, n);
 
 	for (ref = refs; ref; ref = ref->next) {
 		if (ref->status != REF_STATUS_NONE &&
 		    ref->status != REF_STATUS_UPTODATE &&
+		    ref->status != REF_STATUS_REMOTE_CHANGED &&
 		    ref->status != REF_STATUS_OK)
 			n += print_one_push_status(ref, dest, n);
 	}
@@ -522,6 +560,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
 		switch (ref->status) {
 		case REF_STATUS_NONE:
 		case REF_STATUS_UPTODATE:
+		case REF_STATUS_REMOTE_CHANGED:
 		case REF_STATUS_OK:
 			break;
 		default:
diff --git a/cache.h b/cache.h
index 4e59646..ae2427d 100644
--- a/cache.h
+++ b/cache.h
@@ -516,6 +516,7 @@ struct ref {
 		REF_STATUS_UPTODATE,
 		REF_STATUS_REMOTE_REJECT,
 		REF_STATUS_EXPECTING_REPORT,
+		REF_STATUS_REMOTE_CHANGED,
 	} status;
 	char *remote_status;
 	struct ref *peer_ref; /* when renaming */
diff --git a/receive-pack.c b/receive-pack.c
index ed44b89..1101e18 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -18,6 +18,9 @@ static int report_status;
 static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
 
+/* Update hook exit code: hook has updated ref on its own */
+#define EXIT_CODE_REF_UPDATED 100
+
 static int receive_pack_config(const char *var, const char *value)
 {
 	if (strcmp(var, "receive.denynonfastforwards") == 0) {
@@ -70,8 +73,11 @@ static struct command *commands;
 static const char pre_receive_hook[] = "hooks/pre-receive";
 static const char post_receive_hook[] = "hooks/post-receive";
 
-static int hook_status(int code, const char *hook_name)
+static int hook_status(int code, const char *hook_name, int ok_start)
 {
+	if (ok_start && -code >= ok_start)
+		return -code;
+
 	switch (code) {
 	case 0:
 		return 0;
@@ -121,7 +127,7 @@ static int run_hook(const char *hook_name)
 
 	code = start_command(&proc);
 	if (code)
-		return hook_status(code, hook_name);
+		return hook_status(code, hook_name, 0);
 	for (cmd = commands; cmd; cmd = cmd->next) {
 		if (!cmd->error_string) {
 			size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
@@ -132,7 +138,7 @@ static int run_hook(const char *hook_name)
 				break;
 		}
 	}
-	return hook_status(finish_command(&proc), hook_name);
+	return hook_status(finish_command(&proc), hook_name, 0);
 }
 
 static int run_update_hook(struct command *cmd)
@@ -155,7 +161,8 @@ static int run_update_hook(struct command *cmd)
 	proc.no_stdin = 1;
 	proc.stdout_to_stderr = 1;
 
-	return hook_status(run_command(&proc), update_hook);
+	return hook_status(run_command(&proc), update_hook,
+			   EXIT_CODE_REF_UPDATED);
 }
 
 static const char *update(struct command *cmd)
@@ -194,28 +201,41 @@ static const char *update(struct command *cmd)
 			return "non-fast forward";
 		}
 	}
-	if (run_update_hook(cmd)) {
-		error("hook declined to update %s", name);
-		return "hook declined";
-	}
-
-	if (is_null_sha1(new_sha1)) {
-		if (delete_ref(name, old_sha1)) {
-			error("failed to delete %s", name);
-			return "failed to delete";
+	switch (run_update_hook(cmd)) {
+	case 0:
+		if (is_null_sha1(new_sha1)) {
+			if (delete_ref(name, old_sha1)) {
+				error("failed to delete %s", name);
+				return "failed to delete";
+			}
+			fprintf(stderr, "%s: %s -> deleted\n", name,
+				sha1_to_hex(old_sha1));
 		}
-		return NULL; /* good */
-	}
-	else {
-		lock = lock_any_ref_for_update(name, old_sha1, 0);
-		if (!lock) {
-			error("failed to lock %s", name);
-			return "failed to lock";
+		else {
+			lock = lock_any_ref_for_update(name, old_sha1, 0);
+			if (!lock) {
+				error("failed to lock %s", name);
+				return "failed to lock";
+			}
+			if (write_ref_sha1(lock, new_sha1, "push")) {
+				return "failed to write"; /* error() already called */
+			}
 		}
-		if (write_ref_sha1(lock, new_sha1, "push")) {
-			return "failed to write"; /* error() already called */
+		return NULL; /* good */
+
+	case EXIT_CODE_REF_UPDATED:
+		/* hook has taken care of updating ref, which means it
+		   might be a different revision than we think. */
+		if (! resolve_ref(name, new_sha1, 1, NULL)) {
+			error("can't resolve ref %s after hook updated it",
+				name);
+			return "ref not resolvable";
 		}
 		return NULL; /* good */
+
+	default:
+		error("hook declined to update %s", name);
+		return "hook declined";
 	}
 }
 
@@ -415,8 +435,8 @@ static void report(const char *unpack_status)
 		     unpack_status ? unpack_status : "ok");
 	for (cmd = commands; cmd; cmd = cmd->next) {
 		if (!cmd->error_string)
-			packet_write(1, "ok %s\n",
-				     cmd->ref_name);
+			packet_write(1, "ok %s %s\n",
+				     cmd->ref_name, sha1_to_hex(cmd->new_sha1));
 		else
 			packet_write(1, "ng %s %s\n",
 				     cmd->ref_name, cmd->error_string);
-- 
1.5.3.6.2040.g97735-dirty

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

[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]

  Powered by Linux