| #include "cache.h" | |
| #include "refs.h" | |
| #include "pkt-line.h" | |
| #include "run-command.h" | |
| #include <sys/wait.h> | |
| static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; | |
| static const char unpacker[] = "git-unpack-objects"; | |
| static int show_ref(const char *path, const unsigned char *sha1) | |
| { | |
| packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); | |
| return 0; | |
| } | |
| static void write_head_info(void) | |
| { | |
| for_each_ref(show_ref); | |
| } | |
| struct command { | |
| struct command *next; | |
| unsigned char updated; | |
| unsigned char old_sha1[20]; | |
| unsigned char new_sha1[20]; | |
| char ref_name[0]; | |
| }; | |
| static struct command *commands = NULL; | |
| static int is_all_zeroes(const char *hex) | |
| { | |
| int i; | |
| for (i = 0; i < 40; i++) | |
| if (*hex++ != '0') | |
| return 0; | |
| return 1; | |
| } | |
| static int verify_old_ref(const char *name, char *hex_contents) | |
| { | |
| int fd, ret; | |
| char buffer[60]; | |
| if (is_all_zeroes(hex_contents)) | |
| return 0; | |
| fd = open(name, O_RDONLY); | |
| if (fd < 0) | |
| return -1; | |
| ret = read(fd, buffer, 40); | |
| close(fd); | |
| if (ret != 40) | |
| return -1; | |
| if (memcmp(buffer, hex_contents, 40)) | |
| return -1; | |
| 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 int update(const char *name, | |
| unsigned char *old_sha1, unsigned char *new_sha1) | |
| { | |
| char new_hex[60], *old_hex, *lock_name; | |
| int newfd, namelen, written; | |
| namelen = strlen(name); | |
| lock_name = xmalloc(namelen + 10); | |
| memcpy(lock_name, name, namelen); | |
| memcpy(lock_name + namelen, ".lock", 6); | |
| strcpy(new_hex, sha1_to_hex(new_sha1)); | |
| old_hex = sha1_to_hex(old_sha1); | |
| if (!has_sha1_file(new_sha1)) | |
| return error("unpack should have generated %s, " | |
| "but I can't find it!", new_hex); | |
| safe_create_leading_directories(lock_name); | |
| newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); | |
| if (newfd < 0) | |
| return error("unable to create %s (%s)", | |
| lock_name, strerror(errno)); | |
| /* Write the ref with an ending '\n' */ | |
| new_hex[40] = '\n'; | |
| new_hex[41] = 0; | |
| written = write(newfd, new_hex, 41); | |
| /* Remove the '\n' again */ | |
| new_hex[40] = 0; | |
| close(newfd); | |
| if (written != 41) { | |
| unlink(lock_name); | |
| return error("unable to write %s", lock_name); | |
| } | |
| if (verify_old_ref(name, old_hex) < 0) { | |
| unlink(lock_name); | |
| return error("%s changed during push", name); | |
| } | |
| if (run_update_hook(name, old_hex, new_hex)) { | |
| unlink(lock_name); | |
| return error("hook declined to update %s\n", name); | |
| } | |
| else if (rename(lock_name, name) < 0) { | |
| unlink(lock_name); | |
| return error("unable to replace %s", name); | |
| } | |
| else { | |
| fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); | |
| return 0; | |
| } | |
| } | |
| static char update_post_hook[] = "hooks/post-update"; | |
| static void run_update_post_hook(struct command *cmd) | |
| { | |
| struct command *cmd_p; | |
| int argc; | |
| char **argv; | |
| if (access(update_post_hook, X_OK) < 0) | |
| return; | |
| for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { | |
| if (!cmd_p->updated) | |
| continue; | |
| argc++; | |
| } | |
| argv = xmalloc(sizeof(*argv) * (1 + argc)); | |
| argv[0] = update_post_hook; | |
| for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { | |
| if (!cmd_p->updated) | |
| continue; | |
| argv[argc] = xmalloc(strlen(cmd_p->ref_name) + 1); | |
| strcpy(argv[argc], cmd_p->ref_name); | |
| argc++; | |
| } | |
| argv[argc] = NULL; | |
| run_command_v(argc, argv); | |
| } | |
| /* | |
| * This gets called after(if) we've successfully | |
| * unpacked the data payload. | |
| */ | |
| static void execute_commands(void) | |
| { | |
| struct command *cmd = commands; | |
| while (cmd) { | |
| cmd->updated = !update(cmd->ref_name, | |
| cmd->old_sha1, cmd->new_sha1); | |
| cmd = cmd->next; | |
| } | |
| run_update_post_hook(commands); | |
| } | |
| static void read_head_info(void) | |
| { | |
| struct command **p = &commands; | |
| for (;;) { | |
| static char line[1000]; | |
| unsigned char old_sha1[20], new_sha1[20]; | |
| struct command *cmd; | |
| int len; | |
| len = packet_read_line(0, line, sizeof(line)); | |
| if (!len) | |
| break; | |
| if (line[len-1] == '\n') | |
| line[--len] = 0; | |
| if (len < 83 || | |
| line[40] != ' ' || | |
| line[81] != ' ' || | |
| get_sha1_hex(line, old_sha1) || | |
| get_sha1_hex(line + 41, new_sha1)) | |
| die("protocol error: expected old/new/ref, got '%s'", line); | |
| cmd = xmalloc(sizeof(struct command) + len - 80); | |
| memcpy(cmd->old_sha1, old_sha1, 20); | |
| memcpy(cmd->new_sha1, new_sha1, 20); | |
| memcpy(cmd->ref_name, line + 82, len - 81); | |
| cmd->next = NULL; | |
| *p = cmd; | |
| p = &cmd->next; | |
| } | |
| } | |
| static void unpack(void) | |
| { | |
| int code = run_command(unpacker, NULL); | |
| switch (code) { | |
| case 0: | |
| return; | |
| case -ERR_RUN_COMMAND_FORK: | |
| die("unpack fork failed"); | |
| case -ERR_RUN_COMMAND_EXEC: | |
| die("unpack 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: | |
| 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); | |
| } | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int i; | |
| const char *dir = NULL; | |
| argv++; | |
| for (i = 1; i < argc; i++) { | |
| const char *arg = *argv++; | |
| if (*arg == '-') { | |
| /* Do flag handling here */ | |
| usage(receive_pack_usage); | |
| } | |
| if (dir) | |
| usage(receive_pack_usage); | |
| dir = arg; | |
| } | |
| if (!dir) | |
| usage(receive_pack_usage); | |
| /* chdir to the directory. If that fails, try appending ".git" */ | |
| if (chdir(dir) < 0) { | |
| if (chdir(mkpath("%s.git", dir)) < 0) | |
| die("unable to cd to %s", dir); | |
| } | |
| /* If we have a ".git" directory, chdir to it */ | |
| chdir(".git"); | |
| putenv("GIT_DIR=."); | |
| if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0) | |
| die("%s doesn't appear to be a git directory", dir); | |
| write_head_info(); | |
| /* EOF */ | |
| packet_flush(1); | |
| read_head_info(); | |
| if (commands) { | |
| unpack(); | |
| execute_commands(); | |
| } | |
| return 0; | |
| } |