Subject: [PATCH] add post-fetch hook

From: Joey Hess <[email-masked]>
Date: Sat, 24 Dec 2011 19:42:12 -0400

The post-fetch hook is fed on its stdin all refs that were newly fetched.
It is not allowed to abort the fetch (or pull), but can modify what
was fetched or take other actions.

One example use of this hook is to automatically merge certain remote
branches into a local branch. Another is to update a local cache
(such as a search index) with the fetched refs. No other hook is run
near fetch time, except for post-merge, which doesn't always run after a
fetch, which is why this additional hook is useful.

Signed-off-by: Joey Hess <[email-masked]>

---

The #1 point of confusion for git-annex users is the need to run
"git annex merge" after fetching. That does a union merge of newly
fetched remote git-annex branches into the local git-annex branch.
If a user does a "git pull; ... ; git push" and forgets to git annex merge
in between, their push often fails as the git-annex branches have diverged.
With this hook, that confusing step can be eliminated.

Since git annex merge could be run at any point between fetch and push,
I considered several different hooks, including this one, a pre-push hook,
and a variant of this hook that does not feed the hook any information
on stdin. I chose this one, with the information on stdin because it seems
the most generally useful, and will let me make git annex merge slightly
more optimal than it would be without the stdin.

 Documentation/githooks.txt |   12 ++++++++++
 builtin/fetch.c            |   50 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 0 deletions(-)

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 28edefa..96a588c 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -162,6 +162,18 @@ This hook can be used to perform repository validity checks, auto-display
 differences from the previous HEAD if different, or set working dir metadata
 properties.
 
+post-fetch
+~~~~~~~~~~
+
+This hook is invoked by 'git fetch' (commonly called by 'git pull'), after
+refs have been fetched from the remote repository. It takes no arguments,
+but is fed a list of new or updated refs on its standard input. This hook
+cannot affect the outcome of 'git fetch' and is not executed, if nothing
+was fetched.
+
+This hook can make modifications to the fetched refs, or take other
+actions.
+
 post-merge
 ~~~~~~~~~~
 
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 33ad3aa..d813b8e 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -89,6 +89,52 @@ static struct option builtin_fetch_options[] = {
 	OPT_END()
 };
 
+static const char post_fetch_hook[] = "post-fetch";
+struct ref *fetched_refs = NULL;
+void run_post_fetch_hook (void) {
+	struct ref *ref;
+	struct child_process proc;
+	const char *argv[2];
+	FILE *f;
+
+	if (! fetched_refs)
+		return;
+
+	argv[0] = git_path("hooks/%s", post_fetch_hook);
+	if (access(argv[0], X_OK) < 0)
+		return;
+	argv[1] = NULL;
+
+	memset(&proc, 0, sizeof(proc));
+	proc.argv = argv;
+	proc.in = -1;
+	proc.stdout_to_stderr = 1;
+	if (start_command(&proc) != 0)
+		return;
+
+	f = fdopen(proc.in, "w");
+	if (f == NULL) {
+		close(proc.in);
+		goto cleanup;
+	}
+	for (ref = fetched_refs; ref; ref = ref->next)
+		fprintf(f, "%s\n", ref->name);
+	fclose(f);
+
+cleanup:
+	free_refs(fetched_refs);
+	fetched_refs = NULL;
+
+	finish_command(&proc);
+	close(proc.in);
+}
+
+void post_fetch_hook_observe (const struct ref *fetched_ref) {
+	struct ref *ref = copy_ref(fetched_ref);
+	ref->next = fetched_refs;
+	fetched_refs = ref;
+}
+
 static void unlock_pack(void)
 {
 	if (transport)
@@ -233,6 +279,7 @@ static int s_update_ref(const char *action,
 	if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
 		return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
 					  STORE_REF_ERROR_OTHER;
+	post_fetch_hook_observe(ref);
 	return 0;
 }
 
@@ -755,6 +802,9 @@ static int do_fetch(struct transport *transport,
 		free_refs(ref_map);
 	}
 
+	/* Run hook only after fetching all refs. */
+	run_post_fetch_hook();
+
 	return 0;
 }
 
-- 
1.7.7.3