From: Josef Weidendorfer Date: Sun, 31 Jul 2005 19:17:43 +0000 (+0200) Subject: [PATCH] Added hook in git-receive-pack X-Git-Tag: v0.99.3~11 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=b1bf95bba26c8de1581f401dcab1f743bad7fc1d;p=git.git [PATCH] Added hook in git-receive-pack Just before updating a ref, $GIT_DIR/hooks/update refname old-sha1 new-sha1 is called if executable. The hook can decline the ref to be updated by exiting with a non-zero status, or allow it to be updated by exiting with a zero status. The mechanism also allows e.g sending of a mail with pushed commits on the remote repository. Documentation update with an example hook is included. jc: The credits of the basic idea and initial implementation go to Josef, but I ended up rewriting major parts of his patch, so bugs are all mine. Also I changed the semantics for the hook from his original version (which were post-update hook) so that the hook can optionally decline to update the ref, and also can be used to implement the overall cleanups. The latter was primarily to implement a suggestion from Linus that calling update-server-info should be made optional. Signed-off-by: Junio C Hamano --- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index d3a835d4..7f95e440 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -22,6 +22,55 @@ program pair is meant to be used to push updates to remote repository. For pull operations, see 'git-fetch-pack' and 'git-clone-pack'. +The command allows for creation and fast forwarding of sha1 refs +(heads/tags) on the remote end (strictly speaking, it is the +local end receive-pack runs, but to the user who is sitting at +the send-pack end, it is updating the remote. Confused?) + +Before each ref is updated, if $GIT_DIR/hooks/update file exists +and executable, it is called with three parameters: + + $GIT_DIR/hooks/update refname sha1-old sha1-new + +The refname parameter is relative to $GIT_DIR; e.g. for the +master head this is "refs/heads/master". Two sha1 are the +object names for the refname before and after the update. 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 same hook is also called with an empty string as refname and +no other arguments just before git-receive-pack exits. This can +be used to implement repository wide cleanup task if needed. +The exit code from this hook invocation is ignored; the only +thing left for git-receive-pack to do at that point is to exit +itself anyway. + +Using this hook, it is easy to generate mails on updates to +the local repository. This example script sends a mail with +the commits pushed to the repository: + + #!/bin/sh + case "$#,$1" in + 1,) # help packed repository pulled via dumb protocol. + git-update-server-info + ;; + *) # mail out commit update information. + if expr "$2" : '0*$' >/dev/null + then + echo "Created now ref." + git-rev-list --pretty "$2" + else + echo "New commits" + git-rev-list --pretty "$3" "^$2" + fi | + mail -s "Changes to ref $1" commit-list@mydomain + esac + exit 0 OPTIONS ------- diff --git a/Makefile b/Makefile index 1b0ebb4c..b4aa706c 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,9 @@ LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \ LIB_H += rev-cache.h LIB_OBJS += rev-cache.o +LIB_H += run-command.h +LIB_OBJS += run-command.o + LIB_H += strbuf.h LIB_OBJS += strbuf.o diff --git a/receive-pack.c b/receive-pack.c index d4e82bf9..85bf8569 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -1,11 +1,12 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" +#include "run-command.h" #include static const char receive_pack_usage[] = "git-receive-pack "; -static const char *unpacker = "git-unpack-objects"; +static const char unpacker[] = "git-unpack-objects"; static int show_ref(const char *path, const unsigned char *sha1) { @@ -55,6 +56,38 @@ static int verify_old_ref(const char *name, char *hex_contents) return 0; } +static char update_hook[] = "hooks/update"; + +static int run_update_hook(const char *refname, + char *old_hex, char *new_hex) +{ + int code; + + if (access(update_hook, X_OK) < 0) + return 0; + code = run_command(update_hook, refname, old_hex, new_hex, NULL); + switch (code) { + case 0: + return 0; + case -ERR_RUN_COMMAND_FORK: + die("hook fork failed"); + case -ERR_RUN_COMMAND_EXEC: + die("hook execute failed"); + case -ERR_RUN_COMMAND_WAITPID: + die("waitpid failed"); + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + die("waitpid is confused"); + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + fprintf(stderr, "%s died of signal", update_hook); + return -1; + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + die("%s died strangely", update_hook); + default: + error("%s exited with error code %d", update_hook, -code); + return -code; + } +} + static void update(const char *name, unsigned char *old_sha1, unsigned char *new_sha1) { char new_hex[60], *old_hex, *lock_name; @@ -90,11 +123,16 @@ static void update(const char *name, unsigned char *old_sha1, unsigned char *new unlink(lock_name); die("%s changed during push", name); } - if (rename(lock_name, name) < 0) { + if (run_update_hook(name, old_hex, new_hex)) { + unlink(lock_name); + fprintf(stderr, "hook declined to update %s\n", name); + } + else if (rename(lock_name, name) < 0) { unlink(lock_name); die("unable to replace %s", name); } - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); + else + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); } @@ -110,7 +148,7 @@ static void execute_commands(void) update(cmd->ref_name, cmd->old_sha1, cmd->new_sha1); cmd = cmd->next; } - update_server_info(0); + run_update_hook("", NULL, NULL); } static void read_head_info(void) @@ -145,34 +183,24 @@ static void read_head_info(void) static void unpack(void) { - pid_t pid = fork(); - - if (pid < 0) + int code = run_command(unpacker, NULL); + switch (code) { + case 0: + return 0; + case -ERR_RUN_COMMAND_FORK: die("unpack fork failed"); - if (!pid) { - execlp(unpacker, unpacker, NULL); + case -ERR_RUN_COMMAND_EXEC: die("unpack execute failed"); - } - - for (;;) { - int status, code; - int retval = waitpid(pid, &status, 0); - - if (retval < 0) { - if (errno == EINTR) - continue; - die("waitpid failed (%s)", strerror(retval)); - } - if (retval != pid) - die("waitpid is confused"); - if (WIFSIGNALED(status)) - die("%s died of signal %d", unpacker, WTERMSIG(status)); - if (!WIFEXITED(status)) - die("%s died out of really strange complications", unpacker); - code = WEXITSTATUS(status); - if (code) - die("%s exited with error code %d", unpacker, code); - return; + case -ERR_RUN_COMMAND_WAITPID: + die("waitpid failed"); + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + die("waitpid is confused"); + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + die("%s died of signal", unpacker); + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + die("%s died strangely", unpacker); + default: + die("%s exited with error code %d", unpacker, -code); } } diff --git a/run-command.c b/run-command.c new file mode 100644 index 00000000..5117f627 --- /dev/null +++ b/run-command.c @@ -0,0 +1,60 @@ +#include "cache.h" +#include "run-command.h" +#include + +static int run_external_command(int argc, const char **argv) +{ + pid_t pid = fork(); + + if (pid < 0) + return -ERR_RUN_COMMAND_FORK; + if (!pid) { + execvp(argv[0], (char *const*) argv); + return -ERR_RUN_COMMAND_EXEC; + } + for (;;) { + int status, code; + int retval = waitpid(pid, &status, 0); + + if (retval < 0) { + if (errno == EINTR) + continue; + error("waitpid failed (%s)", strerror(retval)); + return -ERR_RUN_COMMAND_WAITPID; + } + if (retval != pid) + return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; + if (WIFSIGNALED(status)) + return -ERR_RUN_COMMAND_WAITPID_SIGNAL; + + if (!WIFEXITED(status)) + return -ERR_RUN_COMMAND_WAITPID_NOEXIT; + code = WEXITSTATUS(status); + if (code) + return -code; + return 0; + } +} + +int run_command(const char *cmd, ...) +{ + int argc; + const char *argv[MAX_RUN_COMMAND_ARGS]; + const char *arg; + va_list param; + + fprintf(stderr, "run-command %s (%d)\n", cmd, ERR_RUN_COMMAND_EXEC); + + va_start(param, cmd); + argv[0] = cmd; + argc = 1; + while (argc < MAX_RUN_COMMAND_ARGS) { + arg = argv[argc++] = va_arg(param, char *); + if (!arg) + break; + } + va_end(param); + if (MAX_RUN_COMMAND_ARGS <= argc) + return error("too many args to run %s", cmd); + return run_external_command(argc, argv); +} diff --git a/run-command.h b/run-command.h new file mode 100644 index 00000000..4f646139 --- /dev/null +++ b/run-command.h @@ -0,0 +1,16 @@ +#ifndef RUN_COMMAND_H +#define RUN_COMMAND_H + +#define MAX_RUN_COMMAND_ARGS 256 +enum { + ERR_RUN_COMMAND_FORK = 10000, + ERR_RUN_COMMAND_EXEC, + ERR_RUN_COMMAND_WAITPID, + ERR_RUN_COMMAND_WAITPID_WRONG_PID, + ERR_RUN_COMMAND_WAITPID_SIGNAL, + ERR_RUN_COMMAND_WAITPID_NOEXIT, +}; + +int run_command(const char *cmd, ...); + +#endif