diff options
Diffstat (limited to 'transport.c')
-rw-r--r-- | transport.c | 774 |
1 files changed, 570 insertions, 204 deletions
diff --git a/transport.c b/transport.c index 644a30a0b2..f080e93dcd 100644 --- a/transport.c +++ b/transport.c @@ -3,11 +3,18 @@ #include "run-command.h" #include "pkt-line.h" #include "fetch-pack.h" +#include "remote.h" +#include "connect.h" #include "send-pack.h" #include "walker.h" #include "bundle.h" #include "dir.h" #include "refs.h" +#include "branch.h" +#include "url.h" +#include "submodule.h" +#include "string-list.h" +#include "sha1-array.h" /* rsync support */ @@ -102,14 +109,15 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) return; for (;;) { - int cmp = cmp, len; + int cmp = 0; /* assigned before used */ + int len; if (!fgets(buffer, sizeof(buffer), f)) { fclose(f); return; } - if (hexval(buffer[0]) > 0xf) + if (!isxdigit(buffer[0])) continue; len = strlen(buffer); if (len && buffer[len - 1] == '\n') @@ -135,16 +143,66 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } +static void set_upstreams(struct transport *transport, struct ref *refs, + int pretend) +{ + struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + const char *localname; + const char *tmp; + const char *remotename; + unsigned char sha[20]; + int flag = 0; + /* + * Check suitability for tracking. Must be successful / + * already up-to-date ref create/modify (not delete). + */ + if (ref->status != REF_STATUS_OK && + ref->status != REF_STATUS_UPTODATE) + continue; + if (!ref->peer_ref) + continue; + if (is_null_sha1(ref->new_sha1)) + continue; + + /* Follow symbolic refs (mainly for HEAD). */ + localname = ref->peer_ref->name; + remotename = ref->name; + tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING, + sha, &flag); + if (tmp && flag & REF_ISSYMREF && + starts_with(tmp, "refs/heads/")) + localname = tmp; + + /* Both source and destination must be local branches. */ + if (!localname || !starts_with(localname, "refs/heads/")) + continue; + if (!remotename || !starts_with(remotename, "refs/heads/")) + continue; + + if (!pretend) + install_branch_config(BRANCH_CONFIG_VERBOSE, + localname + 11, transport->remote->name, + remotename); + else + printf("Would set upstream of '%s' to '%s' of '%s'\n", + localname + 11, remotename + 11, + transport->remote->name); + } +} + static const char *rsync_url(const char *url) { - return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url; + if (!starts_with(url, "rsync://")) + skip_prefix(url, "rsync:", &url); + return url; } static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; - struct ref dummy, *tail = &dummy; - struct child_process rsync; + struct ref dummy = {NULL}, *tail = &dummy; + struct child_process rsync = CHILD_PROCESS_INIT; const char *args[5]; int temp_dir_len; @@ -161,11 +219,10 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addstr(&buf, "/refs"); - memset(&rsync, 0, sizeof(rsync)); rsync.argv = args; rsync.stdout_to_stderr = 1; args[0] = "rsync"; - args[1] = (transport->verbose > 0) ? "-rv" : "-r"; + args[1] = (transport->verbose > 1) ? "-rv" : "-r"; args[2] = buf.buf; args[3] = temp_dir.buf; args[4] = NULL; @@ -204,34 +261,21 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) } static int fetch_objs_via_rsync(struct transport *transport, - int nr_objs, const struct ref **to_fetch) + int nr_objs, struct ref **to_fetch) { - struct strbuf buf = STRBUF_INIT; - struct child_process rsync; - const char *args[8]; - int result; - - strbuf_addstr(&buf, rsync_url(transport->url)); - strbuf_addstr(&buf, "/objects/"); + struct child_process rsync = CHILD_PROCESS_INIT; - memset(&rsync, 0, sizeof(rsync)); - rsync.argv = args; rsync.stdout_to_stderr = 1; - args[0] = "rsync"; - args[1] = (transport->verbose > 0) ? "-rv" : "-r"; - args[2] = "--ignore-existing"; - args[3] = "--exclude"; - args[4] = "info"; - args[5] = buf.buf; - args[6] = get_object_directory(); - args[7] = NULL; + argv_array_push(&rsync.args, "rsync"); + argv_array_push(&rsync.args, (transport->verbose > 1) ? "-rv" : "-r"); + argv_array_push(&rsync.args, "--ignore-existing"); + argv_array_push(&rsync.args, "--exclude"); + argv_array_push(&rsync.args, "info"); + argv_array_pushf(&rsync.args, "%s/objects/", rsync_url(transport->url)); + argv_array_push(&rsync.args, get_object_directory()); /* NEEDSWORK: handle one level of alternates */ - result = run_command(&rsync); - - strbuf_release(&buf); - - return result; + return run_command(&rsync); } static int write_one_ref(const char *name, const unsigned char *sha1, @@ -239,19 +283,17 @@ static int write_one_ref(const char *name, const unsigned char *sha1, { struct strbuf *buf = data; int len = buf->len; - FILE *f; /* when called via for_each_ref(), flags is non-zero */ - if (flags && prefixcmp(name, "refs/heads/") && - prefixcmp(name, "refs/tags/")) + if (flags && !starts_with(name, "refs/heads/") && + !starts_with(name, "refs/tags/")) return 0; strbuf_addstr(buf, name); if (safe_create_leading_directories(buf->buf) || - !(f = fopen(buf->buf, "w")) || - fprintf(f, "%s\n", sha1_to_hex(sha1)) < 0 || - fclose(f)) - return error("problems writing temporary file %s", buf->buf); + write_file(buf->buf, 0, "%s\n", sha1_to_hex(sha1))) + return error("problems writing temporary file %s: %s", + buf->buf, strerror(errno)); strbuf_setlen(buf, len); return 0; } @@ -282,7 +324,7 @@ static int rsync_transport_push(struct transport *transport, { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; int result = 0, i; - struct child_process rsync; + struct child_process rsync = CHILD_PROCESS_INIT; const char *args[10]; if (flags & TRANSPORT_PUSH_MIRROR) @@ -293,7 +335,6 @@ static int rsync_transport_push(struct transport *transport, strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addch(&buf, '/'); - memset(&rsync, 0, sizeof(rsync)); rsync.argv = args; rsync.stdout_to_stderr = 1; i = 0; @@ -301,7 +342,7 @@ static int rsync_transport_push(struct transport *transport, args[i++] = "-a"; if (flags & TRANSPORT_PUSH_DRY_RUN) args[i++] = "--dry-run"; - if (transport->verbose > 0) + if (transport->verbose > 1) args[i++] = "-v"; args[i++] = "--ignore-existing"; args[i++] = "--exclude"; @@ -349,35 +390,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -#ifndef NO_CURL -static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) -{ - const char **argv; - int argc; - - if (flags & TRANSPORT_PUSH_MIRROR) - return error("http transport does not support mirror mode"); - - argv = xmalloc((refspec_nr + 12) * sizeof(char *)); - argv[0] = "http-push"; - argc = 1; - if (flags & TRANSPORT_PUSH_ALL) - argv[argc++] = "--all"; - if (flags & TRANSPORT_PUSH_FORCE) - argv[argc++] = "--force"; - if (flags & TRANSPORT_PUSH_DRY_RUN) - argv[argc++] = "--dry-run"; - if (flags & TRANSPORT_PUSH_VERBOSE) - argv[argc++] = "--verbose"; - argv[argc++] = transport->url; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; - return !!run_command_v_opt(argv, RUN_GIT_CMD); -} - -#endif - struct bundle_transport_data { int fd; struct bundle_header header; @@ -408,10 +420,11 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus } static int fetch_refs_from_bundle(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; - return unbundle(&data->header, data->fd); + return unbundle(&data->header, data->fd, + transport->progress ? BUNDLE_VERBOSE : 0); } static int close_bundle(struct transport *transport) @@ -424,41 +437,47 @@ static int close_bundle(struct transport *transport) } struct git_transport_data { - unsigned thin : 1; - unsigned keep : 1; - unsigned followtags : 1; - int depth; + struct git_transport_options options; struct child_process *conn; int fd[2]; - const char *uploadpack; - const char *receivepack; - struct extra_have_objects extra_have; + unsigned got_remote_heads : 1; + struct sha1_array extra_have; + struct sha1_array shallow; }; -static int set_git_option(struct transport *connection, +static int set_git_option(struct git_transport_options *opts, const char *name, const char *value) { - struct git_transport_data *data = connection->data; if (!strcmp(name, TRANS_OPT_UPLOADPACK)) { - data->uploadpack = value; + opts->uploadpack = value; return 0; } else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) { - data->receivepack = value; + opts->receivepack = value; return 0; } else if (!strcmp(name, TRANS_OPT_THIN)) { - data->thin = !!value; + opts->thin = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) { - data->followtags = !!value; + opts->followtags = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_KEEP)) { - data->keep = !!value; + opts->keep = !!value; + return 0; + } else if (!strcmp(name, TRANS_OPT_UPDATE_SHALLOW)) { + opts->update_shallow = !!value; return 0; } else if (!strcmp(name, TRANS_OPT_DEPTH)) { if (!value) - data->depth = 0; - else - data->depth = atoi(value); + opts->depth = 0; + else { + char *end; + opts->depth = strtol(value, &end, 0); + if (*end) + die("transport: invalid depth option '%s'", value); + } + return 0; + } else if (!strcmp(name, TRANS_OPT_PUSH_CERT)) { + opts->push_cert = !!value; return 0; } return 1; @@ -467,9 +486,15 @@ static int set_git_option(struct transport *connection, static int connect_setup(struct transport *transport, int for_push, int verbose) { struct git_transport_data *data = transport->data; + + if (data->conn) + return 0; + data->conn = git_connect(data->fd, transport->url, - for_push ? data->receivepack : data->uploadpack, + for_push ? data->options.receivepack : + data->options.uploadpack, verbose ? CONNECT_VERBOSE : 0); + return 0; } @@ -479,58 +504,63 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus struct ref *refs; connect_setup(transport, for_push, 0); - get_remote_heads(data->fd[0], &refs, 0, NULL, - for_push ? REF_NORMAL : 0, &data->extra_have); + get_remote_heads(data->fd[0], NULL, 0, &refs, + for_push ? REF_NORMAL : 0, + &data->extra_have, + &data->shallow); + data->got_remote_heads = 1; return refs; } static int fetch_refs_via_pack(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_heads, struct ref **to_fetch) { struct git_transport_data *data = transport->data; - char **heads = xmalloc(nr_heads * sizeof(*heads)); - char **origh = xmalloc(nr_heads * sizeof(*origh)); - const struct ref *refs; + struct ref *refs; char *dest = xstrdup(transport->url); struct fetch_pack_args args; - int i; struct ref *refs_tmp = NULL; memset(&args, 0, sizeof(args)); - args.uploadpack = data->uploadpack; - args.keep_pack = data->keep; + args.uploadpack = data->options.uploadpack; + args.keep_pack = data->options.keep; args.lock_pack = 1; - args.use_thin_pack = data->thin; - args.include_tag = data->followtags; - args.verbose = (transport->verbose > 0); + args.use_thin_pack = data->options.thin; + args.include_tag = data->options.followtags; + args.verbose = (transport->verbose > 1); args.quiet = (transport->verbose < 0); - args.no_progress = args.quiet || (!transport->progress && !isatty(1)); - args.depth = data->depth; - - for (i = 0; i < nr_heads; i++) - origh[i] = heads[i] = xstrdup(to_fetch[i]->name); - - if (!data->conn) { + args.no_progress = !transport->progress; + args.depth = data->options.depth; + args.check_self_contained_and_connected = + data->options.check_self_contained_and_connected; + args.cloning = transport->cloning; + args.update_shallow = data->options.update_shallow; + + if (!data->got_remote_heads) { connect_setup(transport, 0, 0); - get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL); + get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, + NULL, &data->shallow); + data->got_remote_heads = 1; } refs = fetch_pack(&args, data->fd, data->conn, refs_tmp ? refs_tmp : transport->remote_refs, - dest, nr_heads, heads, &transport->pack_lockfile); + dest, to_fetch, nr_heads, &data->shallow, + &transport->pack_lockfile); close(data->fd[0]); close(data->fd[1]); - if (finish_connect(data->conn)) + if (finish_connect(data->conn)) { + free_refs(refs); refs = NULL; + } data->conn = NULL; + data->got_remote_heads = 0; + data->options.self_contained_and_connected = + args.self_contained_and_connected; free_refs(refs_tmp); - - for (i = 0; i < nr_heads; i++) - free(origh[i]); - free(origh); - free(heads); + free_refs(refs); free(dest); return (refs ? 0 : -1); } @@ -550,7 +580,7 @@ static int push_had_errors(struct ref *ref) return 0; } -static int refs_pushed(struct ref *ref) +int transport_refs_pushed(struct ref *ref) { for (; ref; ref = ref->next) { switch(ref->status) { @@ -564,7 +594,7 @@ static int refs_pushed(struct ref *ref) return 0; } -static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) { struct refspec rs; @@ -586,8 +616,6 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref, int verb } } -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) - static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain) { if (porcelain) { @@ -600,7 +628,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str else fprintf(stdout, "%s\n", summary); } else { - fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary); if (from) fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); else @@ -625,7 +653,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain); else if (is_null_sha1(ref->old_sha1)) print_ref_status('*', - (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : + (starts_with(ref->name, "refs/tags/") ? "[new tag]" : "[new branch]"), ref, ref->peer_ref, NULL, porcelain); else { @@ -634,7 +662,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) const char *msg; strcpy(quickref, status_abbrev(ref->old_sha1)); - if (ref->nonfastforward) { + if (ref->forced_update) { strcat(quickref, "..."); type = '+'; msg = "forced update"; @@ -652,7 +680,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain) static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain) { if (!count) - fprintf(stderr, "To %s\n", dest); + fprintf(porcelain ? stdout : stderr, "To %s\n", dest); switch(ref->status) { case REF_STATUS_NONE: @@ -668,7 +696,27 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast forward", porcelain); + "non-fast-forward", porcelain); + break; + case REF_STATUS_REJECT_ALREADY_EXISTS: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "already exists", porcelain); + break; + case REF_STATUS_REJECT_FETCH_FIRST: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "fetch first", porcelain); + break; + case REF_STATUS_REJECT_NEEDS_FORCE: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "needs force", porcelain); + break; + case REF_STATUS_REJECT_STALE: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "stale info", porcelain); + break; + case REF_STATUS_REJECT_SHALLOW: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "new shallow roots not allowed", porcelain); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, @@ -680,6 +728,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i ref->deletion ? NULL : ref->peer_ref, "remote failed to report status", porcelain); break; + case REF_STATUS_ATOMIC_PUSH_FAILED: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "atomic push failed", porcelain); + break; case REF_STATUS_OK: print_ok_ref_status(ref, porcelain); break; @@ -688,11 +740,15 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i return 1; } -static void print_push_status(const char *dest, struct ref *refs, - int verbose, int porcelain, int * nonfastforward) +void transport_print_push_status(const char *dest, struct ref *refs, + int verbose, int porcelain, unsigned int *reject_reasons) { struct ref *ref; int n = 0; + unsigned char head_sha1[20]; + char *head; + + head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL); if (verbose) { for (ref = refs; ref; ref = ref->next) @@ -704,18 +760,29 @@ static void print_push_status(const char *dest, struct ref *refs, if (ref->status == REF_STATUS_OK) n += print_one_push_status(ref, dest, n, porcelain); - *nonfastforward = 0; + *reject_reasons = 0; for (ref = refs; ref; ref = ref->next) { if (ref->status != REF_STATUS_NONE && ref->status != REF_STATUS_UPTODATE && ref->status != REF_STATUS_OK) n += print_one_push_status(ref, dest, n, porcelain); - if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) - *nonfastforward = 1; + if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) { + if (head != NULL && !strcmp(head, ref->name)) + *reject_reasons |= REJECT_NON_FF_HEAD; + else + *reject_reasons |= REJECT_NON_FF_OTHER; + } else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) { + *reject_reasons |= REJECT_ALREADY_EXISTS; + } else if (ref->status == REF_STATUS_REJECT_FETCH_FIRST) { + *reject_reasons |= REJECT_FETCH_FIRST; + } else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) { + *reject_reasons |= REJECT_NEEDS_FORCE; + } } + free(head); } -static void verify_remote_names(int nr_heads, const char **heads) +void transport_verify_remote_names(int nr_heads, const char **heads) { int i; @@ -731,18 +798,10 @@ static void verify_remote_names(int nr_heads, const char **heads) continue; remote = remote ? (remote + 1) : local; - switch (check_ref_format(remote)) { - case 0: /* ok */ - case CHECK_REF_FORMAT_ONELEVEL: - /* ok but a single level -- that is fine for - * a match pattern. - */ - case CHECK_REF_FORMAT_WILDCARD: - /* ok but ends with a pattern-match character */ - continue; - } - die("remote part of refspec is not a valid name in %s", - heads[i]); + if (check_refname_format(remote, + REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN)) + die("remote part of refspec is not a valid name in %s", + heads[i]); } } @@ -752,20 +811,27 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct send_pack_args args; int ret; - if (!data->conn) { + if (!data->got_remote_heads) { struct ref *tmp_refs; connect_setup(transport, 1, 0); - get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL, - NULL); + get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, + NULL, &data->shallow); + data->got_remote_heads = 1; } + memset(&args, 0, sizeof(args)); args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); - args.use_thin_pack = data->thin; - args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); - args.quiet = !!(flags & TRANSPORT_PUSH_QUIET); + args.use_thin_pack = data->options.thin; + args.verbose = (transport->verbose > 0); + args.quiet = (transport->verbose < 0); + args.progress = transport->progress; args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); + args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); + args.push_cert = !!(flags & TRANSPORT_PUSH_CERT); + args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC); + args.url = transport->url; ret = send_pack(&args, data->fd, data->conn, remote_refs, &data->extra_have); @@ -774,15 +840,28 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re close(data->fd[0]); ret |= finish_connect(data->conn); data->conn = NULL; + data->got_remote_heads = 0; return ret; } +static int connect_git(struct transport *transport, const char *name, + const char *executable, int fd[2]) +{ + struct git_transport_data *data = transport->data; + data->conn = git_connect(data->fd, transport->url, + executable, 0); + fd[0] = data->fd[0]; + fd[1] = data->fd[1]; + return 0; +} + static int disconnect_git(struct transport *transport) { struct git_transport_data *data = transport->data; if (data->conn) { - packet_flush(data->fd[1]); + if (data->got_remote_heads) + packet_flush(data->fd[1]); close(data->fd[0]); close(data->fd[1]); finish_connect(data->conn); @@ -792,12 +871,32 @@ static int disconnect_git(struct transport *transport) return 0; } -static int is_local(const char *url) +void transport_take_over(struct transport *transport, + struct child_process *child) { - const char *colon = strchr(url, ':'); - const char *slash = strchr(url, '/'); - return !colon || (slash && slash < colon) || - has_dos_drive_prefix(url); + struct git_transport_data *data; + + if (!transport->smart_options) + die("Bug detected: Taking over transport requires non-NULL " + "smart_options field."); + + data = xcalloc(1, sizeof(*data)); + data->options = *transport->smart_options; + data->conn = child; + data->fd[0] = data->conn->out; + data->fd[1] = data->conn->in; + data->got_remote_heads = 0; + transport->data = data; + + transport->set_option = NULL; + transport->get_refs_list = get_refs_via_connect; + transport->fetch = fetch_refs_via_pack; + transport->push = NULL; + transport->push_refs = git_transport_push; + transport->disconnect = disconnect_git; + transport->smart_options = &(data->options); + + transport->cannot_reuse = 1; } static int is_file(const char *url) @@ -808,52 +907,87 @@ static int is_file(const char *url) return S_ISREG(buf.st_mode); } +static int external_specification_len(const char *url) +{ + return strchr(url, ':') - url; +} + struct transport *transport_get(struct remote *remote, const char *url) { + const char *helper; struct transport *ret = xcalloc(1, sizeof(*ret)); + ret->progress = isatty(2); + + if (!remote) + die("No remote provided to transport_get()"); + + ret->got_remote_refs = 0; ret->remote = remote; + helper = remote->foreign_vcs; + + if (!url && remote->url) + url = remote->url[0]; ret->url = url; - if (!prefixcmp(url, "rsync:")) { + /* maybe it is a foreign URL? */ + if (url) { + const char *p = url; + + while (is_urlschemechar(p == url, *p)) + p++; + if (starts_with(p, "::")) + helper = xstrndup(url, p - url); + } + + if (helper) { + transport_helper_init(ret, helper); + } else if (starts_with(url, "rsync:")) { ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; - - } else if (!prefixcmp(url, "http://") - || !prefixcmp(url, "https://") - || !prefixcmp(url, "ftp://")) { - transport_helper_init(ret, "curl"); -#ifdef NO_CURL - error("git was compiled without libcurl support."); -#else - ret->push = curl_transport_push; -#endif - - } else if (is_local(url) && is_file(url)) { + ret->smart_options = NULL; + } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; ret->get_refs_list = get_refs_from_bundle; ret->fetch = fetch_refs_from_bundle; ret->disconnect = close_bundle; - - } else { + ret->smart_options = NULL; + } else if (!is_url(url) + || starts_with(url, "file://") + || starts_with(url, "git://") + || starts_with(url, "ssh://") + || starts_with(url, "git+ssh://") + || starts_with(url, "ssh+git://")) { + /* These are builtin smart transports. */ struct git_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; - ret->set_option = set_git_option; + ret->set_option = NULL; ret->get_refs_list = get_refs_via_connect; ret->fetch = fetch_refs_via_pack; ret->push_refs = git_transport_push; + ret->connect = connect_git; ret->disconnect = disconnect_git; + ret->smart_options = &(data->options); - data->thin = 1; data->conn = NULL; - data->uploadpack = "git-upload-pack"; - if (remote && remote->uploadpack) - data->uploadpack = remote->uploadpack; - data->receivepack = "git-receive-pack"; - if (remote && remote->receivepack) - data->receivepack = remote->receivepack; + data->got_remote_heads = 0; + } else { + /* Unknown protocol in URL. Pass to external handler. */ + int len = external_specification_len(url); + char *handler = xmemdupz(url, len); + transport_helper_init(ret, handler); + } + + if (ret->smart_options) { + ret->smart_options->thin = 1; + ret->smart_options->uploadpack = "git-upload-pack"; + if (remote->uploadpack) + ret->smart_options->uploadpack = remote->uploadpack; + ret->smart_options->receivepack = "git-receive-pack"; + if (remote->receivepack) + ret->smart_options->receivepack = remote->receivepack; } return ret; @@ -862,55 +996,224 @@ struct transport *transport_get(struct remote *remote, const char *url) int transport_set_option(struct transport *transport, const char *name, const char *value) { + int git_reports = 1, protocol_reports = 1; + + if (transport->smart_options) + git_reports = set_git_option(transport->smart_options, + name, value); + if (transport->set_option) - return transport->set_option(transport, name, value); + protocol_reports = transport->set_option(transport, name, + value); + + /* If either report is 0, report 0 (success). */ + if (!git_reports || !protocol_reports) + return 0; + /* If either reports -1 (invalid value), report -1. */ + if ((git_reports == -1) || (protocol_reports == -1)) + return -1; + /* Otherwise if both report unknown, report unknown. */ return 1; } +void transport_set_verbosity(struct transport *transport, int verbosity, + int force_progress) +{ + if (verbosity >= 1) + transport->verbose = verbosity <= 3 ? verbosity : 3; + if (verbosity < 0) + transport->verbose = -1; + + /** + * Rules used to determine whether to report progress (processing aborts + * when a rule is satisfied): + * + * . Report progress, if force_progress is 1 (ie. --progress). + * . Don't report progress, if force_progress is 0 (ie. --no-progress). + * . Don't report progress, if verbosity < 0 (ie. -q/--quiet ). + * . Report progress if isatty(2) is 1. + **/ + if (force_progress >= 0) + transport->progress = !!force_progress; + else + transport->progress = verbosity >= 0 && isatty(2); +} + +static void die_with_unpushed_submodules(struct string_list *needs_pushing) +{ + int i; + + fprintf(stderr, "The following submodule paths contain changes that can\n" + "not be found on any remote:\n"); + for (i = 0; i < needs_pushing->nr; i++) + printf(" %s\n", needs_pushing->items[i].string); + fprintf(stderr, "\nPlease try\n\n" + " git push --recurse-submodules=on-demand\n\n" + "or cd to the path and use\n\n" + " git push\n\n" + "to push them to a remote.\n\n"); + + string_list_clear(needs_pushing, 0); + + die("Aborting."); +} + +static int run_pre_push_hook(struct transport *transport, + struct ref *remote_refs) +{ + int ret = 0, x; + struct ref *r; + struct child_process proc = CHILD_PROCESS_INIT; + struct strbuf buf; + const char *argv[4]; + + if (!(argv[0] = find_hook("pre-push"))) + return 0; + + argv[1] = transport->remote->name; + argv[2] = transport->url; + argv[3] = NULL; + + proc.argv = argv; + proc.in = -1; + + if (start_command(&proc)) { + finish_command(&proc); + return -1; + } + + strbuf_init(&buf, 256); + + for (r = remote_refs; r; r = r->next) { + if (!r->peer_ref) continue; + if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue; + if (r->status == REF_STATUS_REJECT_STALE) continue; + if (r->status == REF_STATUS_UPTODATE) continue; + + strbuf_reset(&buf); + strbuf_addf( &buf, "%s %s %s %s\n", + r->peer_ref->name, sha1_to_hex(r->new_sha1), + r->name, sha1_to_hex(r->old_sha1)); + + if (write_in_full(proc.in, buf.buf, buf.len) != buf.len) { + ret = -1; + break; + } + } + + strbuf_release(&buf); + + x = close(proc.in); + if (!ret) + ret = x; + + x = finish_command(&proc); + if (!ret) + ret = x; + + return ret; +} + int transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags, - int *nonfastforward) + unsigned int *reject_reasons) { - *nonfastforward = 0; - verify_remote_names(refspec_nr, refspec); + *reject_reasons = 0; + transport_verify_remote_names(refspec_nr, refspec); + + if (transport->push) { + /* Maybe FIXME. But no important transport uses this case. */ + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + die("This transport does not support using --set-upstream"); - if (transport->push) return transport->push(transport, refspec_nr, refspec, flags); - if (transport->push_refs) { - struct ref *remote_refs = - transport->get_refs_list(transport, 1); + } else if (transport->push_refs) { + struct ref *remote_refs; struct ref *local_refs = get_local_heads(); int match_flags = MATCH_REFS_NONE; - int verbose = flags & TRANSPORT_PUSH_VERBOSE; - int quiet = flags & TRANSPORT_PUSH_QUIET; + int verbose = (transport->verbose > 0); + int quiet = (transport->verbose < 0); int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; - int ret; + int pretend = flags & TRANSPORT_PUSH_DRY_RUN; + int push_ret, ret, err; + + if (check_push_refs(local_refs, refspec_nr, refspec) < 0) + return -1; + + remote_refs = transport->get_refs_list(transport, 1); if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; if (flags & TRANSPORT_PUSH_MIRROR) match_flags |= MATCH_REFS_MIRROR; + if (flags & TRANSPORT_PUSH_PRUNE) + match_flags |= MATCH_REFS_PRUNE; + if (flags & TRANSPORT_PUSH_FOLLOW_TAGS) + match_flags |= MATCH_REFS_FOLLOW_TAGS; - if (match_refs(local_refs, &remote_refs, - refspec_nr, refspec, match_flags)) { + if (match_push_refs(local_refs, &remote_refs, + refspec_nr, refspec, match_flags)) { return -1; } - ret = transport->push_refs(transport, remote_refs, flags); + if (transport->smart_options && + transport->smart_options->cas && + !is_empty_cas(transport->smart_options->cas)) + apply_push_cas(transport->smart_options->cas, + transport->remote, remote_refs); + + set_ref_status_for_push(remote_refs, + flags & TRANSPORT_PUSH_MIRROR, + flags & TRANSPORT_PUSH_FORCE); + + if (!(flags & TRANSPORT_PUSH_NO_HOOK)) + if (run_pre_push_hook(transport, remote_refs)) + return -1; + + if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) { + struct ref *ref = remote_refs; + for (; ref; ref = ref->next) + if (!is_null_sha1(ref->new_sha1) && + !push_unpushed_submodules(ref->new_sha1, + transport->remote->name)) + die ("Failed to push all needed submodules!"); + } + + if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND | + TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) { + struct ref *ref = remote_refs; + struct string_list needs_pushing = STRING_LIST_INIT_DUP; + + for (; ref; ref = ref->next) + if (!is_null_sha1(ref->new_sha1) && + find_unpushed_submodules(ref->new_sha1, + transport->remote->name, &needs_pushing)) + die_with_unpushed_submodules(&needs_pushing); + } + + push_ret = transport->push_refs(transport, remote_refs, flags); + err = push_had_errors(remote_refs); + ret = push_ret | err; - if (!quiet || push_had_errors(remote_refs)) - print_push_status(transport->url, remote_refs, + if (!quiet || err) + transport_print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, - nonfastforward); + reject_reasons); + + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + set_upstreams(transport, remote_refs, pretend); if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(transport->remote, ref, verbose); + transport_update_tracking_ref(transport->remote, ref, verbose); } - if (!quiet && !ret && !refs_pushed(remote_refs)) + if (porcelain && !push_ret) + puts("Done"); + else if (!quiet && !ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); + return ret; } return 1; @@ -918,21 +1221,25 @@ int transport_push(struct transport *transport, const struct ref *transport_get_remote_refs(struct transport *transport) { - if (!transport->remote_refs) + if (!transport->got_remote_refs) { transport->remote_refs = transport->get_refs_list(transport, 0); + transport->got_remote_refs = 1; + } + return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, const struct ref *refs) +int transport_fetch_refs(struct transport *transport, struct ref *refs) { int rc; int nr_heads = 0, nr_alloc = 0, nr_refs = 0; - const struct ref **heads = NULL; - const struct ref *rm; + struct ref **heads = NULL; + struct ref *rm; for (rm = refs; rm; rm = rm->next) { nr_refs++; if (rm->peer_ref && + !is_null_sha1(rm->old_sha1) && !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1)) continue; ALLOC_GROW(heads, nr_heads + 1, nr_alloc); @@ -945,7 +1252,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs) * then local and remote refs are likely to still be equal. * Just feed them all to the fetch method in that case. * This condition shouldn't be met in a non-deepening fetch - * (see builtin-fetch.c:quickfetch()). + * (see builtin/fetch.c:quickfetch()). */ heads = xmalloc(nr_refs * sizeof(*heads)); for (rm = refs; rm; rm = rm->next) @@ -953,6 +1260,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs) } rc = transport->fetch(transport, nr_heads, heads); + free(heads); return rc; } @@ -966,6 +1274,15 @@ void transport_unlock_pack(struct transport *transport) } } +int transport_connect(struct transport *transport, const char *name, + const char *exec, int fd[2]) +{ + if (transport->connect) + return transport->connect(transport, name, exec, fd); + else + die("Operation not supported by protocol"); +} + int transport_disconnect(struct transport *transport) { int ret = 0; @@ -976,7 +1293,7 @@ int transport_disconnect(struct transport *transport) } /* - * Strip username (and password) from an url and return + * Strip username (and password) from a URL and return * it in a newly allocated string. */ char *transport_anonymize_url(const char *url) @@ -985,7 +1302,7 @@ char *transport_anonymize_url(const char *url) size_t anon_len, prefix_len = 0; anon_part = strchr(url, '@'); - if (is_local(url) || !anon_part) + if (url_is_local_not_ssh(url) || !anon_part) goto literal_copy; anon_len = strlen(++anon_part); @@ -1022,3 +1339,52 @@ char *transport_anonymize_url(const char *url) literal_copy: return xstrdup(url); } + +struct alternate_refs_data { + alternate_ref_fn *fn; + void *data; +}; + +static int refs_from_alternate_cb(struct alternate_object_database *e, + void *data) +{ + char *other; + size_t len; + struct remote *remote; + struct transport *transport; + const struct ref *extra; + struct alternate_refs_data *cb = data; + + e->name[-1] = '\0'; + other = xstrdup(real_path(e->base)); + e->name[-1] = '/'; + len = strlen(other); + + while (other[len-1] == '/') + other[--len] = '\0'; + if (len < 8 || memcmp(other + len - 8, "/objects", 8)) + goto out; + /* Is this a git repository with refs? */ + memcpy(other + len - 8, "/refs", 6); + if (!is_directory(other)) + goto out; + other[len - 8] = '\0'; + remote = remote_get(other); + transport = transport_get(remote, other); + for (extra = transport_get_remote_refs(transport); + extra; + extra = extra->next) + cb->fn(extra, cb->data); + transport_disconnect(transport); +out: + free(other); + return 0; +} + +void for_each_alternate_ref(alternate_ref_fn fn, void *data) +{ + struct alternate_refs_data cb; + cb.fn = fn; + cb.data = data; + foreach_alt_odb(refs_from_alternate_cb, &cb); +} |