1#include "cache.h"2#include "refs.h"3#include "pkt-line.h"4#include "tag.h"5#include "object.h"67static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";89#define OUR_REF (1U << 1)10#define WANTED (1U << 2)11#define MAX_HAS 25612#define MAX_NEEDS 25613static int nr_has = 0, nr_needs = 0, nr_our_refs = 0;14static unsigned char has_sha1[MAX_HAS][20];15static unsigned char needs_sha1[MAX_NEEDS][20];16static unsigned int timeout = 0;1718static void reset_timeout(void)19{20alarm(timeout);21}2223static int strip(char *line, int len)24{25if (len && line[len-1] == '\n')26line[--len] = 0;27return len;28}2930static void create_pack_file(void)31{32int fd[2];33pid_t pid;34int create_full_pack = (nr_our_refs == nr_needs && !nr_has);3536if (pipe(fd) < 0)37die("git-upload-pack: unable to create pipe");38pid = fork();39if (pid < 0)40die("git-upload-pack: unable to fork git-rev-list");4142if (!pid) {43int i;44int args;45char **argv;46char *buf;47char **p;4849if (create_full_pack)50args = 10;51else52args = nr_has + nr_needs + 5;53argv = xmalloc(args * sizeof(char *));54buf = xmalloc(args * 45);55p = argv;5657dup2(fd[1], 1);58close(0);59close(fd[0]);60close(fd[1]);61*p++ = "git-rev-list";62*p++ = "--objects";63if (MAX_NEEDS <= nr_needs)64*p++ = "--all";65else {66for (i = 0; i < nr_needs; i++) {67*p++ = buf;68memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);69buf += 41;70}71}72for (i = 0; i < nr_has; i++) {73*p++ = buf;74*buf++ = '^';75memcpy(buf, sha1_to_hex(has_sha1[i]), 41);76buf += 41;77}78*p++ = NULL;79execvp("git-rev-list", argv);80die("git-upload-pack: unable to exec git-rev-list");81}82dup2(fd[0], 0);83close(fd[0]);84close(fd[1]);85execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);86die("git-upload-pack: unable to exec git-pack-objects");87}8889static int got_sha1(char *hex, unsigned char *sha1)90{91int nr;92if (get_sha1_hex(hex, sha1))93die("git-upload-pack: expected SHA1 object, got '%s'", hex);94if (!has_sha1_file(sha1))95return 0;96nr = nr_has;97if (nr < MAX_HAS) {98memcpy(has_sha1[nr], sha1, 20);99nr_has = nr+1;100}101return 1;102}103104static int get_common_commits(void)105{106static char line[1000];107unsigned char sha1[20];108int len;109110for(;;) {111len = packet_read_line(0, line, sizeof(line));112reset_timeout();113114if (!len) {115packet_write(1, "NAK\n");116continue;117}118len = strip(line, len);119if (!strncmp(line, "have ", 5)) {120if (got_sha1(line+5, sha1)) {121packet_write(1, "ACK %s\n", sha1_to_hex(sha1));122break;123}124continue;125}126if (!strcmp(line, "done")) {127packet_write(1, "NAK\n");128return -1;129}130die("git-upload-pack: expected SHA1 list, got '%s'", line);131}132133for (;;) {134len = packet_read_line(0, line, sizeof(line));135reset_timeout();136if (!len)137continue;138len = strip(line, len);139if (!strncmp(line, "have ", 5)) {140got_sha1(line+5, sha1);141continue;142}143if (!strcmp(line, "done"))144break;145die("git-upload-pack: expected SHA1 list, got '%s'", line);146}147return 0;148}149150static int receive_needs(void)151{152static char line[1000];153int len, needs;154155needs = 0;156for (;;) {157struct object *o;158unsigned char dummy[20], *sha1_buf;159len = packet_read_line(0, line, sizeof(line));160reset_timeout();161if (!len)162return needs;163164sha1_buf = dummy;165if (needs == MAX_NEEDS) {166fprintf(stderr,167"warning: supporting only a max of %d requests. "168"sending everything instead.\n",169MAX_NEEDS);170}171else if (needs < MAX_NEEDS)172sha1_buf = needs_sha1[needs];173174if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))175die("git-upload-pack: protocol error, "176"expected to get sha, not '%s'", line);177178/* We have sent all our refs already, and the other end179* should have chosen out of them; otherwise they are180* asking for nonsense.181*182* Hmph. We may later want to allow "want" line that183* asks for something like "master~10" (symbolic)...184* would it make sense? I don't know.185*/186o = lookup_object(sha1_buf);187if (!o || !(o->flags & OUR_REF))188die("git-upload-pack: not our ref %s", line+5);189if (!(o->flags & WANTED)) {190o->flags |= WANTED;191needs++;192}193}194}195196static int send_ref(const char *refname, const unsigned char *sha1)197{198struct object *o = parse_object(sha1);199200packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);201if (!(o->flags & OUR_REF)) {202o->flags |= OUR_REF;203nr_our_refs++;204}205if (o->type == tag_type) {206o = deref_tag(o);207packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);208}209return 0;210}211212static int upload_pack(void)213{214reset_timeout();215head_ref(send_ref);216for_each_ref(send_ref);217packet_flush(1);218nr_needs = receive_needs();219if (!nr_needs)220return 0;221get_common_commits();222create_pack_file();223return 0;224}225226int main(int argc, char **argv)227{228const char *dir;229int i;230int strict = 0;231232for (i = 1; i < argc; i++) {233char *arg = argv[i];234235if (arg[0] != '-')236break;237if (!strcmp(arg, "--strict")) {238strict = 1;239continue;240}241if (!strncmp(arg, "--timeout=", 10)) {242timeout = atoi(arg+10);243continue;244}245if (!strcmp(arg, "--")) {246i++;247break;248}249}250251if (i != argc-1)252usage(upload_pack_usage);253dir = argv[i];254255/* chdir to the directory. If that fails, try appending ".git" */256if (chdir(dir) < 0) {257if (strict || chdir(mkpath("%s.git", dir)) < 0)258die("git-upload-pack unable to chdir to %s", dir);259}260if (!strict)261chdir(".git");262263if (access("objects", X_OK) || access("refs", X_OK))264die("git-upload-pack: %s doesn't seem to be a git archive", dir);265266putenv("GIT_DIR=.");267upload_pack();268return 0;269}