upload-pack.con commit Merge branch 'fixes' (8ac3a61)
   1#include "cache.h"
   2#include "refs.h"
   3#include "pkt-line.h"
   4#include "tag.h"
   5#include "object.h"
   6
   7static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
   8
   9#define MAX_HAS 256
  10#define MAX_NEEDS 256
  11static int nr_has = 0, nr_needs = 0;
  12static unsigned char has_sha1[MAX_HAS][20];
  13static unsigned char needs_sha1[MAX_NEEDS][20];
  14static unsigned int timeout = 0;
  15
  16static void reset_timeout(void)
  17{
  18        alarm(timeout);
  19}
  20
  21static int strip(char *line, int len)
  22{
  23        if (len && line[len-1] == '\n')
  24                line[--len] = 0;
  25        return len;
  26}
  27
  28static void create_pack_file(void)
  29{
  30        int fd[2];
  31        pid_t pid;
  32
  33        if (pipe(fd) < 0)
  34                die("git-upload-pack: unable to create pipe");
  35        pid = fork();
  36        if (pid < 0)
  37                die("git-upload-pack: unable to fork git-rev-list");
  38
  39        if (!pid) {
  40                int i;
  41                int args;
  42                char **argv;
  43                char *buf;
  44                char **p;
  45
  46                if (MAX_NEEDS <= nr_needs)
  47                        args = nr_has + 10;
  48                else
  49                        args = nr_has + nr_needs + 5;
  50                argv = xmalloc(args * sizeof(char *));
  51                buf = xmalloc(args * 45);
  52                p = argv;
  53
  54                dup2(fd[1], 1);
  55                close(0);
  56                close(fd[0]);
  57                close(fd[1]);
  58                *p++ = "git-rev-list";
  59                *p++ = "--objects";
  60                if (MAX_NEEDS <= nr_needs)
  61                        *p++ = "--all";
  62                else {
  63                        for (i = 0; i < nr_needs; i++) {
  64                                *p++ = buf;
  65                                memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
  66                                buf += 41;
  67                        }
  68                }
  69                for (i = 0; i < nr_has; i++) {
  70                        *p++ = buf;
  71                        *buf++ = '^';
  72                        memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
  73                        buf += 41;
  74                }
  75                *p++ = NULL;
  76                execvp("git-rev-list", argv);
  77                die("git-upload-pack: unable to exec git-rev-list");
  78        }
  79        dup2(fd[0], 0);
  80        close(fd[0]);
  81        close(fd[1]);
  82        execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
  83        die("git-upload-pack: unable to exec git-pack-objects");
  84}
  85
  86static int got_sha1(char *hex, unsigned char *sha1)
  87{
  88        int nr;
  89        if (get_sha1_hex(hex, sha1))
  90                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
  91        if (!has_sha1_file(sha1))
  92                return 0;
  93        nr = nr_has;
  94        if (nr < MAX_HAS) {
  95                memcpy(has_sha1[nr], sha1, 20);
  96                nr_has = nr+1;
  97        }
  98        return 1;
  99}
 100
 101static int get_common_commits(void)
 102{
 103        static char line[1000];
 104        unsigned char sha1[20];
 105        int len;
 106
 107        for(;;) {
 108                len = packet_read_line(0, line, sizeof(line));
 109                reset_timeout();
 110
 111                if (!len) {
 112                        packet_write(1, "NAK\n");
 113                        continue;
 114                }
 115                len = strip(line, len);
 116                if (!strncmp(line, "have ", 5)) {
 117                        if (got_sha1(line+5, sha1)) {
 118                                packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
 119                                break;
 120                        }
 121                        continue;
 122                }
 123                if (!strcmp(line, "done")) {
 124                        packet_write(1, "NAK\n");
 125                        return -1;
 126                }
 127                die("git-upload-pack: expected SHA1 list, got '%s'", line);
 128        }
 129
 130        for (;;) {
 131                len = packet_read_line(0, line, sizeof(line));
 132                reset_timeout();
 133                if (!len)
 134                        continue;
 135                len = strip(line, len);
 136                if (!strncmp(line, "have ", 5)) {
 137                        got_sha1(line+5, sha1);
 138                        continue;
 139                }
 140                if (!strcmp(line, "done"))
 141                        break;
 142                die("git-upload-pack: expected SHA1 list, got '%s'", line);
 143        }
 144        return 0;
 145}
 146
 147static int receive_needs(void)
 148{
 149        static char line[1000];
 150        int len, needs;
 151
 152        needs = 0;
 153        for (;;) {
 154                unsigned char dummy[20], *sha1_buf;
 155                len = packet_read_line(0, line, sizeof(line));
 156                reset_timeout();
 157                if (!len)
 158                        return needs;
 159
 160                sha1_buf = dummy;
 161                if (needs == MAX_NEEDS) {
 162                        fprintf(stderr,
 163                                "warning: supporting only a max of %d requests. "
 164                                "sending everything instead.\n",
 165                                MAX_NEEDS);
 166                }
 167                else if (needs < MAX_NEEDS)
 168                        sha1_buf = needs_sha1[needs];
 169
 170                if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
 171                        die("git-upload-pack: protocol error, "
 172                            "expected to get sha, not '%s'", line);
 173                needs++;
 174        }
 175}
 176
 177static int send_ref(const char *refname, const unsigned char *sha1)
 178{
 179        struct object *o = parse_object(sha1);
 180
 181        packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
 182        if (o->type == tag_type) {
 183                o = deref_tag(o);
 184                packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
 185        }
 186        return 0;
 187}
 188
 189static int upload_pack(void)
 190{
 191        reset_timeout();
 192        head_ref(send_ref);
 193        for_each_ref(send_ref);
 194        packet_flush(1);
 195        nr_needs = receive_needs();
 196        if (!nr_needs)
 197                return 0;
 198        get_common_commits();
 199        create_pack_file();
 200        return 0;
 201}
 202
 203int main(int argc, char **argv)
 204{
 205        const char *dir;
 206        int i;
 207        int strict = 0;
 208
 209        for (i = 1; i < argc; i++) {
 210                char *arg = argv[i];
 211
 212                if (arg[0] != '-')
 213                        break;
 214                if (!strcmp(arg, "--strict")) {
 215                        strict = 1;
 216                        continue;
 217                }
 218                if (!strncmp(arg, "--timeout=", 10)) {
 219                        timeout = atoi(arg+10);
 220                        continue;
 221                }
 222                if (!strcmp(arg, "--")) {
 223                        i++;
 224                        break;
 225                }
 226        }
 227        
 228        if (i != argc-1)
 229                usage(upload_pack_usage);
 230        dir = argv[i];
 231
 232        /* chdir to the directory. If that fails, try appending ".git" */
 233        if (chdir(dir) < 0) {
 234                if (strict || chdir(mkpath("%s.git", dir)) < 0)
 235                        die("git-upload-pack unable to chdir to %s", dir);
 236        }
 237        if (!strict)
 238                chdir(".git");
 239
 240        if (access("objects", X_OK) || access("refs", X_OK))
 241                die("git-upload-pack: %s doesn't seem to be a git archive", dir);
 242
 243        putenv("GIT_DIR=.");
 244        upload_pack();
 245        return 0;
 246}