remote-curl.con commit Discover refs via smart HTTP server when available (97cc7bc)
   1#include "cache.h"
   2#include "remote.h"
   3#include "strbuf.h"
   4#include "walker.h"
   5#include "http.h"
   6#include "exec_cmd.h"
   7#include "run-command.h"
   8#include "pkt-line.h"
   9
  10static struct remote *remote;
  11static const char *url;
  12static struct walker *walker;
  13
  14struct options {
  15        int verbosity;
  16        unsigned long depth;
  17        unsigned progress : 1,
  18                followtags : 1,
  19                dry_run : 1;
  20};
  21static struct options options;
  22
  23static void init_walker(void)
  24{
  25        if (!walker)
  26                walker = get_http_walker(url, remote);
  27}
  28
  29static int set_option(const char *name, const char *value)
  30{
  31        if (!strcmp(name, "verbosity")) {
  32                char *end;
  33                int v = strtol(value, &end, 10);
  34                if (value == end || *end)
  35                        return -1;
  36                options.verbosity = v;
  37                return 0;
  38        }
  39        else if (!strcmp(name, "progress")) {
  40                if (!strcmp(value, "true"))
  41                        options.progress = 1;
  42                else if (!strcmp(value, "false"))
  43                        options.progress = 0;
  44                else
  45                        return -1;
  46                return 1 /* TODO implement later */;
  47        }
  48        else if (!strcmp(name, "depth")) {
  49                char *end;
  50                unsigned long v = strtoul(value, &end, 10);
  51                if (value == end || *end)
  52                        return -1;
  53                options.depth = v;
  54                return 1 /* TODO implement later */;
  55        }
  56        else if (!strcmp(name, "followtags")) {
  57                if (!strcmp(value, "true"))
  58                        options.followtags = 1;
  59                else if (!strcmp(value, "false"))
  60                        options.followtags = 0;
  61                else
  62                        return -1;
  63                return 1 /* TODO implement later */;
  64        }
  65        else if (!strcmp(name, "dry-run")) {
  66                if (!strcmp(value, "true"))
  67                        options.dry_run = 1;
  68                else if (!strcmp(value, "false"))
  69                        options.dry_run = 0;
  70                else
  71                        return -1;
  72                return 0;
  73        }
  74        else {
  75                return 1 /* unsupported */;
  76        }
  77}
  78
  79struct discovery {
  80        const char *service;
  81        char *buf_alloc;
  82        char *buf;
  83        size_t len;
  84        unsigned proto_git : 1;
  85};
  86static struct discovery *last_discovery;
  87
  88static void free_discovery(struct discovery *d)
  89{
  90        if (d) {
  91                if (d == last_discovery)
  92                        last_discovery = NULL;
  93                free(d->buf_alloc);
  94                free(d);
  95        }
  96}
  97
  98static struct discovery* discover_refs(const char *service)
  99{
 100        struct strbuf buffer = STRBUF_INIT;
 101        struct discovery *last = last_discovery;
 102        char *refs_url;
 103        int http_ret, is_http = 0;
 104
 105        if (last && !strcmp(service, last->service))
 106                return last;
 107        free_discovery(last);
 108
 109        strbuf_addf(&buffer, "%s/info/refs", url);
 110        if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) {
 111                is_http = 1;
 112                if (!strchr(url, '?'))
 113                        strbuf_addch(&buffer, '?');
 114                else
 115                        strbuf_addch(&buffer, '&');
 116                strbuf_addf(&buffer, "service=%s", service);
 117        }
 118        refs_url = strbuf_detach(&buffer, NULL);
 119
 120        init_walker();
 121        http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
 122        switch (http_ret) {
 123        case HTTP_OK:
 124                break;
 125        case HTTP_MISSING_TARGET:
 126                die("%s not found: did you run git update-server-info on the"
 127                    " server?", refs_url);
 128        default:
 129                http_error(refs_url, http_ret);
 130                die("HTTP request failed");
 131        }
 132
 133        last= xcalloc(1, sizeof(*last_discovery));
 134        last->service = service;
 135        last->buf_alloc = strbuf_detach(&buffer, &last->len);
 136        last->buf = last->buf_alloc;
 137
 138        if (is_http && 5 <= last->len && last->buf[4] == '#') {
 139                /* smart HTTP response; validate that the service
 140                 * pkt-line matches our request.
 141                 */
 142                struct strbuf exp = STRBUF_INIT;
 143
 144                if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
 145                        die("%s has invalid packet header", refs_url);
 146                if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
 147                        strbuf_setlen(&buffer, buffer.len - 1);
 148
 149                strbuf_addf(&exp, "# service=%s", service);
 150                if (strbuf_cmp(&exp, &buffer))
 151                        die("invalid server response; got '%s'", buffer.buf);
 152                strbuf_release(&exp);
 153
 154                /* The header can include additional metadata lines, up
 155                 * until a packet flush marker.  Ignore these now, but
 156                 * in the future we might start to scan them.
 157                 */
 158                strbuf_reset(&buffer);
 159                while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
 160                        strbuf_reset(&buffer);
 161
 162                last->proto_git = 1;
 163        }
 164
 165        free(refs_url);
 166        strbuf_release(&buffer);
 167        last_discovery = last;
 168        return last;
 169}
 170
 171static int write_discovery(int fd, void *data)
 172{
 173        struct discovery *heads = data;
 174        int err = 0;
 175        if (write_in_full(fd, heads->buf, heads->len) != heads->len)
 176                err = 1;
 177        close(fd);
 178        return err;
 179}
 180
 181static struct ref *parse_git_refs(struct discovery *heads)
 182{
 183        struct ref *list = NULL;
 184        struct async async;
 185
 186        memset(&async, 0, sizeof(async));
 187        async.proc = write_discovery;
 188        async.data = heads;
 189
 190        if (start_async(&async))
 191                die("cannot start thread to parse advertised refs");
 192        get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
 193        close(async.out);
 194        if (finish_async(&async))
 195                die("ref parsing thread failed");
 196        return list;
 197}
 198
 199static struct ref *parse_info_refs(struct discovery *heads)
 200{
 201        char *data, *start, *mid;
 202        char *ref_name;
 203        int i = 0;
 204
 205        struct ref *refs = NULL;
 206        struct ref *ref = NULL;
 207        struct ref *last_ref = NULL;
 208
 209        data = heads->buf;
 210        start = NULL;
 211        mid = data;
 212        while (i < heads->len) {
 213                if (!start) {
 214                        start = &data[i];
 215                }
 216                if (data[i] == '\t')
 217                        mid = &data[i];
 218                if (data[i] == '\n') {
 219                        data[i] = 0;
 220                        ref_name = mid + 1;
 221                        ref = xmalloc(sizeof(struct ref) +
 222                                      strlen(ref_name) + 1);
 223                        memset(ref, 0, sizeof(struct ref));
 224                        strcpy(ref->name, ref_name);
 225                        get_sha1_hex(start, ref->old_sha1);
 226                        if (!refs)
 227                                refs = ref;
 228                        if (last_ref)
 229                                last_ref->next = ref;
 230                        last_ref = ref;
 231                        start = NULL;
 232                }
 233                i++;
 234        }
 235
 236        init_walker();
 237        ref = alloc_ref("HEAD");
 238        if (!walker->fetch_ref(walker, ref) &&
 239            !resolve_remote_symref(ref, refs)) {
 240                ref->next = refs;
 241                refs = ref;
 242        } else {
 243                free(ref);
 244        }
 245
 246        return refs;
 247}
 248
 249static struct ref *get_refs(int for_push)
 250{
 251        struct discovery *heads;
 252
 253        if (for_push)
 254                heads = discover_refs("git-receive-pack");
 255        else
 256                heads = discover_refs("git-upload-pack");
 257
 258        if (heads->proto_git)
 259                return parse_git_refs(heads);
 260        return parse_info_refs(heads);
 261}
 262
 263static void output_refs(struct ref *refs)
 264{
 265        struct ref *posn;
 266        for (posn = refs; posn; posn = posn->next) {
 267                if (posn->symref)
 268                        printf("@%s %s\n", posn->symref, posn->name);
 269                else
 270                        printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name);
 271        }
 272        printf("\n");
 273        fflush(stdout);
 274        free_refs(refs);
 275}
 276
 277static int fetch_dumb(int nr_heads, struct ref **to_fetch)
 278{
 279        char **targets = xmalloc(nr_heads * sizeof(char*));
 280        int ret, i;
 281
 282        for (i = 0; i < nr_heads; i++)
 283                targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
 284
 285        init_walker();
 286        walker->get_all = 1;
 287        walker->get_tree = 1;
 288        walker->get_history = 1;
 289        walker->get_verbosely = options.verbosity >= 3;
 290        walker->get_recover = 0;
 291        ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
 292
 293        for (i = 0; i < nr_heads; i++)
 294                free(targets[i]);
 295        free(targets);
 296
 297        return ret ? error("Fetch failed.") : 0;
 298}
 299
 300static void parse_fetch(struct strbuf *buf)
 301{
 302        struct ref **to_fetch = NULL;
 303        struct ref *list_head = NULL;
 304        struct ref **list = &list_head;
 305        int alloc_heads = 0, nr_heads = 0;
 306
 307        do {
 308                if (!prefixcmp(buf->buf, "fetch ")) {
 309                        char *p = buf->buf + strlen("fetch ");
 310                        char *name;
 311                        struct ref *ref;
 312                        unsigned char old_sha1[20];
 313
 314                        if (strlen(p) < 40 || get_sha1_hex(p, old_sha1))
 315                                die("protocol error: expected sha/ref, got %s'", p);
 316                        if (p[40] == ' ')
 317                                name = p + 41;
 318                        else if (!p[40])
 319                                name = "";
 320                        else
 321                                die("protocol error: expected sha/ref, got %s'", p);
 322
 323                        ref = alloc_ref(name);
 324                        hashcpy(ref->old_sha1, old_sha1);
 325
 326                        *list = ref;
 327                        list = &ref->next;
 328
 329                        ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads);
 330                        to_fetch[nr_heads++] = ref;
 331                }
 332                else
 333                        die("http transport does not support %s", buf->buf);
 334
 335                strbuf_reset(buf);
 336                if (strbuf_getline(buf, stdin, '\n') == EOF)
 337                        return;
 338                if (!*buf->buf)
 339                        break;
 340        } while (1);
 341
 342        if (fetch_dumb(nr_heads, to_fetch))
 343                exit(128); /* error already reported */
 344        free_refs(list_head);
 345        free(to_fetch);
 346
 347        printf("\n");
 348        fflush(stdout);
 349        strbuf_reset(buf);
 350}
 351
 352static int push_dav(int nr_spec, char **specs)
 353{
 354        const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
 355        int argc = 0, i;
 356
 357        argv[argc++] = "http-push";
 358        argv[argc++] = "--helper-status";
 359        if (options.dry_run)
 360                argv[argc++] = "--dry-run";
 361        if (options.verbosity > 1)
 362                argv[argc++] = "--verbose";
 363        argv[argc++] = url;
 364        for (i = 0; i < nr_spec; i++)
 365                argv[argc++] = specs[i];
 366        argv[argc++] = NULL;
 367
 368        if (run_command_v_opt(argv, RUN_GIT_CMD))
 369                die("git-%s failed", argv[0]);
 370        free(argv);
 371        return 0;
 372}
 373
 374static void parse_push(struct strbuf *buf)
 375{
 376        char **specs = NULL;
 377        int alloc_spec = 0, nr_spec = 0, i;
 378
 379        do {
 380                if (!prefixcmp(buf->buf, "push ")) {
 381                        ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
 382                        specs[nr_spec++] = xstrdup(buf->buf + 5);
 383                }
 384                else
 385                        die("http transport does not support %s", buf->buf);
 386
 387                strbuf_reset(buf);
 388                if (strbuf_getline(buf, stdin, '\n') == EOF)
 389                        return;
 390                if (!*buf->buf)
 391                        break;
 392        } while (1);
 393
 394        if (push_dav(nr_spec, specs))
 395                exit(128); /* error already reported */
 396        for (i = 0; i < nr_spec; i++)
 397                free(specs[i]);
 398        free(specs);
 399
 400        printf("\n");
 401        fflush(stdout);
 402}
 403
 404int main(int argc, const char **argv)
 405{
 406        struct strbuf buf = STRBUF_INIT;
 407
 408        git_extract_argv0_path(argv[0]);
 409        setup_git_directory();
 410        if (argc < 2) {
 411                fprintf(stderr, "Remote needed\n");
 412                return 1;
 413        }
 414
 415        options.verbosity = 1;
 416        options.progress = !!isatty(2);
 417
 418        remote = remote_get(argv[1]);
 419
 420        if (argc > 2) {
 421                url = argv[2];
 422        } else {
 423                url = remote->url[0];
 424        }
 425
 426        do {
 427                if (strbuf_getline(&buf, stdin, '\n') == EOF)
 428                        break;
 429                if (!prefixcmp(buf.buf, "fetch ")) {
 430                        parse_fetch(&buf);
 431
 432                } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
 433                        int for_push = !!strstr(buf.buf + 4, "for-push");
 434                        output_refs(get_refs(for_push));
 435
 436                } else if (!prefixcmp(buf.buf, "push ")) {
 437                        parse_push(&buf);
 438
 439                } else if (!prefixcmp(buf.buf, "option ")) {
 440                        char *name = buf.buf + strlen("option ");
 441                        char *value = strchr(name, ' ');
 442                        int result;
 443
 444                        if (value)
 445                                *value++ = '\0';
 446                        else
 447                                value = "true";
 448
 449                        result = set_option(name, value);
 450                        if (!result)
 451                                printf("ok\n");
 452                        else if (result < 0)
 453                                printf("error invalid value\n");
 454                        else
 455                                printf("unsupported\n");
 456                        fflush(stdout);
 457
 458                } else if (!strcmp(buf.buf, "capabilities")) {
 459                        printf("fetch\n");
 460                        printf("option\n");
 461                        printf("push\n");
 462                        printf("\n");
 463                        fflush(stdout);
 464                } else {
 465                        return 1;
 466                }
 467                strbuf_reset(&buf);
 468        } while (1);
 469        return 0;
 470}