From 073b0921bb5988628e7af423924c410f522f403a Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 26 Dec 2011 10:53:27 -0400 Subject: [PATCH 2/2] add tweak-fetch hook The tweak-fetch hook is fed lines on stdin for all refs that were fetched, and outputs on stdout possibly modified lines. Its output is parsed and used when git fetch updates the remote tracking refs, records the entries in FETCH_HEAD, and produces its report. --- Documentation/githooks.txt | 29 +++++++ builtin/fetch.c | 191 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 1 deletions(-) diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 28edefa..be2624c 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -162,6 +162,35 @@ 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. +tweak-fetch +~~~~~~~~~~ + +This hook is invoked by 'git fetch' (commonly called by 'git pull'), after +refs have been fetched from the remote repository. It is not executed, if +nothing was fetched. + +The output of the hook is used to update the remote-tracking branches, and +`.git/FETCH_HEAD`, in preparation for for a later merge operation done by +'git merge'. + +It takes no arguments, but is fed a line of the following format on +its standard input for each ref that was fetched. + + SP not-for-merge|merge SP SP LF + +Where the "not-for-merge" flag indicates the ref is not to be merged into the +current branch, and the "merge" flag indicates that 'git merge' should +later merge it. The `` is the remote's name for the ref +that was pulled, and `` is a name of a remote-tracking branch, +like "refs/remotes/origin/master", or can be empty if the fetched ref is not +being stored in a local refname. + +The hook must consume all of its standard input, and output back lines +of the same format. It can modify its input as desired, including +adding or removing lines, updating the sha1 (i.e. re-point the +remote-tracking branch), changing the merge flag, and changing the +`` (i.e. use different remote-tracking branch). + post-merge ~~~~~~~~~~ diff --git a/builtin/fetch.c b/builtin/fetch.c index 70b9f89..5434b6f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -103,6 +103,194 @@ static int add_existing(const char *refname, const unsigned char *sha1, return 0; } +static const char tweak_fetch_hook[] = "tweak-fetch"; + +int feed_tweak_fetch_hook (int in, int out, void *data) +{ + struct ref *ref; + struct strbuf buf = STRBUF_INIT; + int ret; + + for (ref = data; ref; ref = ref->next) { + strbuf_addstr(&buf, sha1_to_hex(ref->old_sha1)); + strbuf_addch(&buf, ' '); + strbuf_addstr(&buf, ref->merge ? "merge" : "not-for-merge"); + strbuf_addch(&buf, ' '); + if (ref->name) + strbuf_addstr(&buf, ref->name); + strbuf_addch(&buf, ' '); + if (ref->peer_ref && ref->peer_ref->name) + strbuf_addstr(&buf, ref->peer_ref->name); + strbuf_addch(&buf, '\n'); + } + + ret = write_in_full(out, buf.buf, buf.len) != buf.len; + if (ret) + warning("%s hook failed to consume all its input", + tweak_fetch_hook); + close(out); + strbuf_release(&buf); + return ret; +} + +struct ref *parse_tweak_fetch_hook_line (char *l, + struct string_list *existing_refs) +{ + struct ref *ref = NULL, *peer_ref = NULL; + struct string_list_item *peer_item = NULL; + char *words[4]; + int i, word=0; + char *problem; + + for (i=0; l[i]; i++) { + if (isspace(l[i])) { + l[i]='\0'; + words[word]=l; + l+=i+1; + i=0; + word++; + if (word > 3) { + problem="too many words"; + goto unparsable; + } + } + } + if (word < 3) { + problem="not enough words"; + goto unparsable; + } + + ref = alloc_ref(words[2]); + peer_ref = ref->peer_ref = alloc_ref(l); + ref->peer_ref->force=1; + + if (get_sha1_hex(words[0], ref->old_sha1)) { + problem="bad sha1"; + goto unparsable; + } + + if (strcmp(words[1], "merge") == 0) { + ref->merge=1; + } + else if (strcmp(words[1], "not-for-merge") != 0) { + problem="bad merge flag"; + goto unparsable; + } + + peer_item = string_list_lookup(existing_refs, peer_ref->name); + if (peer_item) + hashcpy(peer_ref->old_sha1, peer_item->util); + + return ref; + + unparsable: + warning("%s hook output a wrongly formed line: %s", + tweak_fetch_hook, problem); + free(ref); + free(peer_ref); + return NULL; +} + +struct refs_result read_tweak_fetch_hook (int in) { + struct refs_result res; + FILE *f; + struct strbuf buf; + struct string_list existing_refs = STRING_LIST_INIT_NODUP; + struct ref *ref, *prevref=NULL; + + res.status = 0; + res.new_refs = NULL; + + f = fdopen(in, "r"); + if (f == NULL) { + res.status = 1; + return res; + } + + strbuf_init(&buf, 128); + for_each_ref(add_existing, &existing_refs); + + while (strbuf_getline(&buf, f, '\n') != EOF) { + char *l = strbuf_detach(&buf, NULL); + ref = parse_tweak_fetch_hook_line(l, &existing_refs); + if (ref) { + if (prevref) { + prevref->next=ref; + prevref=ref; + } + else { + res.new_refs = prevref = ref; + } + } + else { + res.status = 1; + } + free(l); + } + + string_list_clear(&existing_refs, 0); + strbuf_release(&buf); + fclose(f); + return res; +} + +/* The hook is fed lines of the form: + * SP SP SP LF + * And should output rewritten lines of the same form. + */ +struct ref *run_tweak_fetch_hook (struct ref *fetched_refs) +{ + struct child_process hook; + const char *argv[2]; + struct async async; + struct refs_result res; + + if (! fetched_refs) + return fetched_refs; + + argv[0] = git_path("hooks/%s", tweak_fetch_hook); + if (access(argv[0], X_OK) < 0) + return fetched_refs; + argv[1] = NULL; + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.in = -1; + hook.out = -1; + if (start_command(&hook)) + return fetched_refs; + + /* Use an async writer to feed the hook process. + * This allows the hook to read and write a line at + * a time without blocking. */ + memset(&async, 0, sizeof(async)); + async.proc = feed_tweak_fetch_hook; + async.data = fetched_refs; + async.out = hook.in; + if (start_async(&async)) { + close(hook.in); + close(hook.out); + finish_command(&hook); + return fetched_refs; + } + res = read_tweak_fetch_hook(hook.out); + res.status |= finish_async(&async); + res.status |= finish_command(&hook); + + if (res.status) { + warning("%s hook failed, ignoring its output", tweak_fetch_hook); + free(res.new_refs); + return fetched_refs; + } + else { + /* The new_refs are returned, to be used in place of + * fetched_refs, so it is not needed anymore and can + * be freed here. */ + free_refs(fetched_refs); + return res.new_refs; + } +} + static void unlock_pack(void) { if (transport) @@ -529,7 +717,8 @@ static struct refs_result fetch_refs(struct transport *transport, if (res.status) res.status = transport_fetch_refs(transport, ref_map); if (!res.status) { - res.new_refs = ref_map + res.new_refs = run_tweak_fetch_hook(ref_map); + res.status |= store_updated_refs(transport->url, transport->remote->name, res.new_refs); -- 1.7.7.3