#include "cache.h"
#include "transport.h"
#include "run-command.h"
-#ifndef NO_CURL
-#include "http.h"
-#endif
#include "pkt-line.h"
#include "fetch-pack.h"
#include "send-pack.h"
return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
}
-static struct ref *get_refs_via_rsync(struct transport *transport)
+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;
const char *args[5];
int temp_dir_len;
+ if (for_push)
+ return NULL;
+
/* copy the refs to the temporary directory */
strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
if (!mkdtemp(temp_dir.buf))
- die ("Could not make temporary directory");
+ die_errno ("Could not make temporary directory");
temp_dir_len = temp_dir.len;
strbuf_addstr(&buf, rsync_url(transport->url));
strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
if (!mkdtemp(temp_dir.buf))
- die ("Could not make temporary directory");
+ die_errno ("Could not make temporary directory");
strbuf_addch(&temp_dir, '/');
if (flags & TRANSPORT_PUSH_ALL) {
return result;
}
-/* Generic functions for using commit walkers */
-
-#ifndef NO_CURL /* http fetch is the only user */
-static int fetch_objs_via_walker(struct transport *transport,
- int nr_objs, const struct ref **to_fetch)
-{
- char *dest = xstrdup(transport->url);
- struct walker *walker = transport->data;
- char **objs = xmalloc(nr_objs * sizeof(*objs));
- int i;
-
- walker->get_all = 1;
- walker->get_tree = 1;
- walker->get_history = 1;
- walker->get_verbosely = transport->verbose >= 0;
- walker->get_recover = 0;
-
- for (i = 0; i < nr_objs; i++)
- objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
-
- if (walker_fetch(walker, nr_objs, objs, NULL, NULL))
- die("Fetch failed.");
-
- for (i = 0; i < nr_objs; i++)
- free(objs[i]);
- free(objs);
- free(dest);
- return 0;
-}
-#endif /* NO_CURL */
-
-static int disconnect_walker(struct transport *transport)
-{
- struct walker *walker = transport->data;
- if (walker)
- walker_free(walker);
- return 0;
-}
-
#ifndef NO_CURL
static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
{
const char **argv;
int argc;
- int err;
if (flags & TRANSPORT_PUSH_MIRROR)
return error("http transport does not support mirror mode");
while (refspec_nr--)
argv[argc++] = *refspec++;
argv[argc] = NULL;
- err = run_command_v_opt(argv, RUN_GIT_CMD);
- switch (err) {
- case -ERR_RUN_COMMAND_FORK:
- error("unable to fork for %s", argv[0]);
- case -ERR_RUN_COMMAND_EXEC:
- error("unable to exec %s", argv[0]);
- break;
- case -ERR_RUN_COMMAND_WAITPID:
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- error("%s died with strange error", argv[0]);
- }
- return !!err;
-}
-
-static struct ref *get_refs_via_curl(struct transport *transport)
-{
- struct strbuf buffer = STRBUF_INIT;
- char *data, *start, *mid;
- char *ref_name;
- char *refs_url;
- int i = 0;
-
- struct active_request_slot *slot;
- struct slot_results results;
-
- struct ref *refs = NULL;
- struct ref *ref = NULL;
- struct ref *last_ref = NULL;
-
- struct walker *walker;
-
- if (!transport->data)
- transport->data = get_http_walker(transport->url,
- transport->remote);
-
- walker = transport->data;
-
- refs_url = xmalloc(strlen(transport->url) + 11);
- sprintf(refs_url, "%s/info/refs", transport->url);
-
- slot = get_active_slot();
- slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, refs_url);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
-
- if (start_active_slot(slot)) {
- run_active_slot(slot);
- if (results.curl_result != CURLE_OK) {
- strbuf_release(&buffer);
- if (missing_target(&results))
- die("%s not found: did you run git update-server-info on the server?", refs_url);
- else
- die("%s download error - %s", refs_url, curl_errorstr);
- }
- } else {
- strbuf_release(&buffer);
- die("Unable to start HTTP request");
- }
-
- data = buffer.buf;
- start = NULL;
- mid = data;
- while (i < buffer.len) {
- if (!start)
- start = &data[i];
- if (data[i] == '\t')
- mid = &data[i];
- if (data[i] == '\n') {
- data[i] = 0;
- ref_name = mid + 1;
- ref = xmalloc(sizeof(struct ref) +
- strlen(ref_name) + 1);
- memset(ref, 0, sizeof(struct ref));
- strcpy(ref->name, ref_name);
- get_sha1_hex(start, ref->old_sha1);
- if (!refs)
- refs = ref;
- if (last_ref)
- last_ref->next = ref;
- last_ref = ref;
- start = NULL;
- }
- i++;
- }
-
- strbuf_release(&buffer);
-
- ref = alloc_ref("HEAD");
- if (!walker->fetch_ref(walker, ref) &&
- !resolve_remote_symref(ref, refs)) {
- ref->next = refs;
- refs = ref;
- } else {
- free(ref);
- }
-
- return refs;
-}
-
-static int fetch_objs_via_curl(struct transport *transport,
- int nr_objs, const struct ref **to_fetch)
-{
- if (!transport->data)
- transport->data = get_http_walker(transport->url,
- transport->remote);
- return fetch_objs_via_walker(transport, nr_objs, to_fetch);
+ return !!run_command_v_opt(argv, RUN_GIT_CMD);
}
#endif
struct bundle_header header;
};
-static struct ref *get_refs_from_bundle(struct transport *transport)
+static struct ref *get_refs_from_bundle(struct transport *transport, int for_push)
{
struct bundle_transport_data *data = transport->data;
struct ref *result = NULL;
int i;
+ if (for_push)
+ return NULL;
+
if (data->fd > 0)
close(data->fd);
data->fd = read_bundle_header(transport->url, &data->header);
int fd[2];
const char *uploadpack;
const char *receivepack;
+ struct extra_have_objects extra_have;
};
static int set_git_option(struct transport *connection,
return 1;
}
-static int connect_setup(struct transport *transport)
+static int connect_setup(struct transport *transport, int for_push, int verbose)
{
struct git_transport_data *data = transport->data;
- data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0);
+ data->conn = git_connect(data->fd, transport->url,
+ for_push ? data->receivepack : data->uploadpack,
+ verbose ? CONNECT_VERBOSE : 0);
return 0;
}
-static struct ref *get_refs_via_connect(struct transport *transport)
+static struct ref *get_refs_via_connect(struct transport *transport, int for_push)
{
struct git_transport_data *data = transport->data;
struct ref *refs;
- connect_setup(transport);
- get_remote_heads(data->fd[0], &refs, 0, NULL, 0, NULL);
+ connect_setup(transport, for_push, 0);
+ get_remote_heads(data->fd[0], &refs, 0, NULL,
+ for_push ? REF_NORMAL : 0, &data->extra_have);
return refs;
}
origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
if (!data->conn) {
- connect_setup(transport);
+ connect_setup(transport, 0, 0);
get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
}
return (refs ? 0 : -1);
}
-static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+static int push_had_errors(struct ref *ref)
+{
+ for (; ref; ref = ref->next) {
+ switch (ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ case REF_STATUS_OK:
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int refs_pushed(struct ref *ref)
+{
+ for (; ref; ref = ref->next) {
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ case REF_STATUS_UPTODATE:
+ break;
+ default:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+{
+ struct refspec rs;
+
+ if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
+ return;
+
+ rs.src = ref->name;
+ rs.dst = NULL;
+
+ if (!remote_find_tracking(remote, &rs)) {
+ if (verbose)
+ fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+ if (ref->deletion) {
+ delete_ref(rs.dst, NULL, 0);
+ } else
+ update_ref("update by push", rs.dst,
+ ref->new_sha1, NULL, 0, 0);
+ free(rs.dst);
+ }
+}
+
+#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) {
+ if (from)
+ fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to->name);
+ else
+ fprintf(stdout, "%c\t:%s\t", flag, to->name);
+ if (msg)
+ fprintf(stdout, "%s (%s)\n", summary, msg);
+ else
+ fprintf(stdout, "%s\n", summary);
+ } else {
+ fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+ if (from)
+ fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
+ else
+ fputs(prettify_refname(to->name), stderr);
+ if (msg) {
+ fputs(" (", stderr);
+ fputs(msg, stderr);
+ fputc(')', stderr);
+ }
+ fputc('\n', stderr);
+ }
+}
+
+static const char *status_abbrev(unsigned char sha1[20])
+{
+ return find_unique_abbrev(sha1, DEFAULT_ABBREV);
+}
+
+static void print_ok_ref_status(struct ref *ref, int porcelain)
+{
+ if (ref->deletion)
+ 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]" :
+ "[new branch]"),
+ ref, ref->peer_ref, NULL, porcelain);
+ else {
+ char quickref[84];
+ char type;
+ const char *msg;
+
+ strcpy(quickref, status_abbrev(ref->old_sha1));
+ if (ref->nonfastforward) {
+ strcat(quickref, "...");
+ type = '+';
+ msg = "forced update";
+ } else {
+ strcat(quickref, "..");
+ type = ' ';
+ msg = NULL;
+ }
+ strcat(quickref, status_abbrev(ref->new_sha1));
+
+ print_ref_status(type, quickref, ref, ref->peer_ref, msg, 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);
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ print_ref_status('X', "[no match]", ref, NULL, NULL, porcelain);
+ break;
+ case REF_STATUS_REJECT_NODELETE:
+ print_ref_status('!', "[rejected]", ref, NULL,
+ "remote does not support deleting refs", porcelain);
+ break;
+ case REF_STATUS_UPTODATE:
+ print_ref_status('=', "[up to date]", ref,
+ ref->peer_ref, NULL, porcelain);
+ break;
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+ "non-fast forward", porcelain);
+ break;
+ case REF_STATUS_REMOTE_REJECT:
+ print_ref_status('!', "[remote rejected]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ ref->remote_status, porcelain);
+ break;
+ case REF_STATUS_EXPECTING_REPORT:
+ print_ref_status('!', "[remote failure]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ "remote failed to report status", porcelain);
+ break;
+ case REF_STATUS_OK:
+ print_ok_ref_status(ref, porcelain);
+ break;
+ }
+
+ return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs,
+ int verbose, int porcelain, int * nonfastforward)
+{
+ struct ref *ref;
+ int n = 0;
+
+ if (verbose) {
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_UPTODATE)
+ n += print_one_push_status(ref, dest, n, porcelain);
+ }
+
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n, porcelain);
+
+ *nonfastforward = 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;
+ }
+}
+
+static void verify_remote_names(int nr_heads, const char **heads)
+{
+ int i;
+
+ for (i = 0; i < nr_heads; i++) {
+ const char *local = heads[i];
+ const char *remote = strrchr(heads[i], ':');
+
+ if (*local == '+')
+ local++;
+
+ /* A matching refspec is okay. */
+ if (remote == local && remote[1] == '\0')
+ 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]);
+ }
+}
+
+static int git_transport_push(struct transport *transport, struct ref *remote_refs, int flags)
{
struct git_transport_data *data = transport->data;
struct send_pack_args args;
+ int ret;
+
+ if (!data->conn) {
+ struct ref *tmp_refs;
+ connect_setup(transport, 1, 0);
+
+ get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
+ NULL);
+ }
- args.receivepack = data->receivepack;
- args.send_all = !!(flags & TRANSPORT_PUSH_ALL);
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.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
- return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec);
+ ret = send_pack(&args, data->fd, data->conn, remote_refs,
+ &data->extra_have);
+
+ close(data->fd[1]);
+ close(data->fd[0]);
+ ret |= finish_connect(data->conn);
+ data->conn = NULL;
+
+ return ret;
}
static int disconnect_git(struct transport *transport)
{
struct transport *ret = xcalloc(1, sizeof(*ret));
+ if (!remote)
+ die("No remote provided to transport_get()");
+
ret->remote = remote;
ret->url = url;
} 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->get_refs_list = get_refs_via_curl;
- ret->fetch = fetch_objs_via_curl;
ret->push = curl_transport_push;
#endif
- ret->disconnect = disconnect_walker;
} else if (is_local(url) && is_file(url)) {
struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
ret->set_option = set_git_option;
ret->get_refs_list = get_refs_via_connect;
ret->fetch = fetch_refs_via_pack;
- ret->push = git_transport_push;
+ ret->push_refs = git_transport_push;
ret->disconnect = disconnect_git;
data->thin = 1;
data->conn = NULL;
data->uploadpack = "git-upload-pack";
- if (remote && remote->uploadpack)
+ if (remote->uploadpack)
data->uploadpack = remote->uploadpack;
data->receivepack = "git-receive-pack";
- if (remote && remote->receivepack)
+ if (remote->receivepack)
data->receivepack = remote->receivepack;
}
}
int transport_push(struct transport *transport,
- int refspec_nr, const char **refspec, int flags)
+ int refspec_nr, const char **refspec, int flags,
+ int *nonfastforward)
{
- if (!transport->push)
- return 1;
- return transport->push(transport, refspec_nr, refspec, flags);
+ *nonfastforward = 0;
+ verify_remote_names(refspec_nr, refspec);
+
+ 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);
+ 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 porcelain = flags & TRANSPORT_PUSH_PORCELAIN;
+ int ret;
+
+ if (flags & TRANSPORT_PUSH_ALL)
+ match_flags |= MATCH_REFS_ALL;
+ if (flags & TRANSPORT_PUSH_MIRROR)
+ match_flags |= MATCH_REFS_MIRROR;
+
+ if (match_refs(local_refs, &remote_refs,
+ refspec_nr, refspec, match_flags)) {
+ return -1;
+ }
+
+ ret = transport->push_refs(transport, remote_refs, flags);
+
+ if (!quiet || push_had_errors(remote_refs))
+ print_push_status(transport->url, remote_refs,
+ verbose | porcelain, porcelain,
+ nonfastforward);
+
+ if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next)
+ update_tracking_ref(transport->remote, ref, verbose);
+ }
+
+ if (!quiet && !ret && !refs_pushed(remote_refs))
+ fprintf(stderr, "Everything up-to-date\n");
+ return ret;
+ }
+ return 1;
}
const struct ref *transport_get_remote_refs(struct transport *transport)
{
if (!transport->remote_refs)
- transport->remote_refs = transport->get_refs_list(transport);
+ transport->remote_refs = transport->get_refs_list(transport, 0);
return transport->remote_refs;
}
int transport_fetch_refs(struct transport *transport, const struct ref *refs)
{
int rc;
- int nr_heads = 0, nr_alloc = 0;
+ int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
const struct ref **heads = NULL;
const struct ref *rm;
for (rm = refs; rm; rm = rm->next) {
+ nr_refs++;
if (rm->peer_ref &&
!hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
continue;
heads[nr_heads++] = rm;
}
+ if (!nr_heads) {
+ /*
+ * When deepening of a shallow repository is requested,
+ * 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()).
+ */
+ heads = xmalloc(nr_refs * sizeof(*heads));
+ for (rm = refs; rm; rm = rm->next)
+ heads[nr_heads++] = rm;
+ }
+
rc = transport->fetch(transport, nr_heads, heads);
free(heads);
return rc;
void transport_unlock_pack(struct transport *transport)
{
if (transport->pack_lockfile) {
- unlink(transport->pack_lockfile);
+ unlink_or_warn(transport->pack_lockfile);
free(transport->pack_lockfile);
transport->pack_lockfile = NULL;
}
free(transport);
return ret;
}
+
+/*
+ * Strip username (and password) from an url and return
+ * it in a newly allocated string.
+ */
+char *transport_anonymize_url(const char *url)
+{
+ char *anon_url, *scheme_prefix, *anon_part;
+ size_t anon_len, prefix_len = 0;
+
+ anon_part = strchr(url, '@');
+ if (is_local(url) || !anon_part)
+ goto literal_copy;
+
+ anon_len = strlen(++anon_part);
+ scheme_prefix = strstr(url, "://");
+ if (!scheme_prefix) {
+ if (!strchr(anon_part, ':'))
+ /* cannot be "me@there:/path/name" */
+ goto literal_copy;
+ } else {
+ const char *cp;
+ /* make sure scheme is reasonable */
+ for (cp = url; cp < scheme_prefix; cp++) {
+ switch (*cp) {
+ /* RFC 1738 2.1 */
+ case '+': case '.': case '-':
+ break; /* ok */
+ default:
+ if (isalnum(*cp))
+ break;
+ /* it isn't */
+ goto literal_copy;
+ }
+ }
+ /* @ past the first slash does not count */
+ cp = strchr(scheme_prefix + 3, '/');
+ if (cp && cp < anon_part)
+ goto literal_copy;
+ prefix_len = scheme_prefix - url + 3;
+ }
+ anon_url = xcalloc(1, 1 + prefix_len + anon_len);
+ memcpy(anon_url, url, prefix_len);
+ memcpy(anon_url + prefix_len, anon_part, anon_len);
+ return anon_url;
+literal_copy:
+ return xstrdup(url);
+}