#include "send-pack.h"
static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"git-send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
" --all and explicit <ref> specification are mutually exclusive.";
static struct send_pack_args args = {
refs = refs->next;
}
+ close(po.in);
if (finish_command(&po))
return error("pack-objects died with strange error");
return 0;
for_each_ref(one_local_ref, NULL);
}
-static int receive_status(int in)
+static int receive_status(int in, struct ref *refs)
{
+ struct ref *hint;
char line[1000];
int ret = 0;
int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7)) {
- fprintf(stderr, "did not receive status back\n");
- return -1;
- }
+ if (len < 10 || memcmp(line, "unpack ", 7))
+ return error("did not receive remote status");
if (memcmp(line, "unpack ok\n", 10)) {
- fputs(line, stderr);
+ char *p = line + strlen(line) - 1;
+ if (*p == '\n')
+ *p = '\0';
+ error("unpack failed: %s", line + 7);
ret = -1;
}
+ hint = NULL;
while (1) {
+ char *refname;
+ char *msg;
len = packet_read_line(in, line, sizeof(line));
if (!len)
break;
if (len < 3 ||
- (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) {
+ (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
fprintf(stderr, "protocol error: %s\n", line);
ret = -1;
break;
}
- if (!memcmp(line, "ok", 2))
+
+ line[strlen(line)-1] = '\0';
+ refname = line + 3;
+ msg = strchr(refname, ' ');
+ if (msg)
+ *msg++ = '\0';
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_ref_by_name(hint, refname);
+ if (!hint)
+ hint = find_ref_by_name(refs, refname);
+ if (!hint) {
+ warning("remote reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+ warning("remote reported status on unexpected ref: %s",
+ refname);
continue;
- fputs(line, stderr);
- ret = -1;
+ }
+
+ if (line[0] == 'o' && line[1] == 'k')
+ hint->status = REF_STATUS_OK;
+ else {
+ hint->status = REF_STATUS_REMOTE_REJECT;
+ ret = -1;
+ }
+ if (msg)
+ hint->remote_status = xstrdup(msg);
+ /* start our next search from the next ref */
+ hint = hint->next;
}
return ret;
}
static void update_tracking_ref(struct remote *remote, struct ref *ref)
{
struct refspec rs;
- int will_delete_ref;
- rs.src = ref->name;
- rs.dst = NULL;
-
- if (!ref->peer_ref)
+ if (ref->status != REF_STATUS_OK)
return;
- will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
-
- if (!will_delete_ref &&
- !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1))
- return;
+ rs.src = ref->name;
+ rs.dst = NULL;
if (!remote_find_tracking(remote, &rs)) {
- fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
- if (is_null_sha1(ref->peer_ref->new_sha1)) {
+ if (args.verbose)
+ fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+ if (ref->deletion) {
if (delete_ref(rs.dst, NULL))
error("Failed to delete");
} else
}
}
-static const char *prettify_ref(const char *name)
+static const char *prettify_ref(const struct ref *ref)
{
+ const char *name = ref->name;
return name + (
!prefixcmp(name, "refs/heads/") ? 11 :
!prefixcmp(name, "refs/tags/") ? 10 :
#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)
+{
+ fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+ if (from)
+ fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
+ else
+ fputs(prettify_ref(to), 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)
+{
+ if (ref->deletion)
+ print_ref_status('-', "[deleted]", ref, NULL, NULL);
+ 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);
+ 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);
+ }
+}
+
+static int print_one_push_status(struct ref *ref, const char *dest, int count)
+{
+ if (!count)
+ fprintf(stderr, "To %s\n", dest);
+
+ switch(ref->status) {
+ case REF_STATUS_NONE:
+ print_ref_status('X', "[no match]", ref, NULL, NULL);
+ break;
+ case REF_STATUS_REJECT_NODELETE:
+ print_ref_status('!', "[rejected]", ref, NULL,
+ "remote does not support deleting refs");
+ break;
+ case REF_STATUS_UPTODATE:
+ print_ref_status('=', "[up to date]", ref,
+ ref->peer_ref, NULL);
+ break;
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+ "non-fast forward");
+ break;
+ case REF_STATUS_REMOTE_REJECT:
+ print_ref_status('!', "[remote rejected]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ ref->remote_status);
+ break;
+ case REF_STATUS_EXPECTING_REPORT:
+ print_ref_status('!', "[remote failure]", ref,
+ ref->deletion ? NULL : ref->peer_ref,
+ "remote failed to report status");
+ break;
+ case REF_STATUS_OK:
+ print_ok_ref_status(ref);
+ break;
+ }
+
+ return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs)
+{
+ struct ref *ref;
+ int n = 0;
+
+ if (args.verbose) {
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_UPTODATE)
+ n += print_one_push_status(ref, dest, n);
+ }
+
+ for (ref = refs; ref; ref = ref->next)
+ if (ref->status == REF_STATUS_OK)
+ n += print_one_push_status(ref, dest, n);
+
+ 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);
+ }
+}
+
+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 int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
{
struct ref *ref;
int new_refs;
- int ret = 0;
int ask_for_status_report = 0;
int allow_deleting_refs = 0;
int expect_status_report = 0;
- int shown_dest = 0;
+ int flags = MATCH_REFS_NONE;
+ int ret;
+
+ if (args.send_all)
+ flags |= MATCH_REFS_ALL;
+ if (args.send_mirror)
+ flags |= MATCH_REFS_MIRROR;
/* No funny business with the matcher */
remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL);
if (!remote_tail)
remote_tail = &remote_refs;
if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, args.send_all))
+ nr_refspec, refspec, flags)) {
+ close(out);
return -1;
+ }
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
"Perhaps you should specify a branch such as 'master'.\n");
+ close(out);
return 0;
}
*/
new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
- char old_hex[60], *new_hex;
- int will_delete_ref;
- const char *pretty_ref;
- const char *pretty_peer;
+ const unsigned char *new_sha1;
- if (!ref->peer_ref)
- continue;
-
- if (!shown_dest) {
- fprintf(stderr, "To %s\n", dest);
- shown_dest = 1;
+ if (!ref->peer_ref) {
+ if (!args.send_mirror)
+ continue;
+ new_sha1 = null_sha1;
}
+ else
+ new_sha1 = ref->peer_ref->new_sha1;
- pretty_ref = prettify_ref(ref->name);
- pretty_peer = prettify_ref(ref->peer_ref->name);
- will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
- if (will_delete_ref && !allow_deleting_refs) {
- fprintf(stderr, " ! %-*s %s (remote does not support deleting refs)\n",
- SUMMARY_WIDTH, "[rejected]", pretty_ref);
- ret = -2;
+ ref->deletion = is_null_sha1(new_sha1);
+ if (ref->deletion && !allow_deleting_refs) {
+ ref->status = REF_STATUS_REJECT_NODELETE;
continue;
}
- if (!will_delete_ref &&
- !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
- if (args.verbose)
- fprintf(stderr, " = %-*s %s -> %s\n",
- SUMMARY_WIDTH, "[up to date]",
- pretty_peer, pretty_ref);
+ if (!ref->deletion &&
+ !hashcmp(ref->old_sha1, new_sha1)) {
+ ref->status = REF_STATUS_UPTODATE;
continue;
}
* always allowed.
*/
- if (!args.force_update &&
- !will_delete_ref &&
+ ref->nonfastforward =
+ !ref->deletion &&
!is_null_sha1(ref->old_sha1) &&
- !ref->force) {
- if (!has_sha1_file(ref->old_sha1) ||
- !ref_newer(ref->peer_ref->new_sha1,
- ref->old_sha1)) {
- /* We do not have the remote ref, or
- * we know that the remote ref is not
- * an ancestor of what we are trying to
- * push. Either way this can be losing
- * commits at the remote end and likely
- * we were not up to date to begin with.
- */
- fprintf(stderr, " ! %-*s %s -> %s (non-fast forward)\n",
- SUMMARY_WIDTH, "[rejected]",
- pretty_peer, pretty_ref);
- ret = -2;
- continue;
- }
+ (!has_sha1_file(ref->old_sha1)
+ || !ref_newer(new_sha1, ref->old_sha1));
+
+ if (ref->nonfastforward && !ref->force && !args.force_update) {
+ ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+ continue;
}
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
- if (!will_delete_ref)
+
+ hashcpy(ref->new_sha1, new_sha1);
+ if (!ref->deletion)
new_refs++;
- strcpy(old_hex, sha1_to_hex(ref->old_sha1));
- new_hex = sha1_to_hex(ref->new_sha1);
if (!args.dry_run) {
+ char *old_hex = sha1_to_hex(ref->old_sha1);
+ char *new_hex = sha1_to_hex(ref->new_sha1);
+
if (ask_for_status_report) {
packet_write(out, "%s %s %s%c%s",
old_hex, new_hex, ref->name, 0,
packet_write(out, "%s %s %s",
old_hex, new_hex, ref->name);
}
- if (will_delete_ref)
- fprintf(stderr, " - %-*s %s\n",
- SUMMARY_WIDTH, "[deleting]",
- pretty_ref);
- else if (is_null_sha1(ref->old_sha1)) {
- const char *msg;
-
- if (!prefixcmp(ref->name, "refs/tags/"))
- msg = "[new tag]";
- else
- msg = "[new branch]";
- fprintf(stderr, " * %-*s %s -> %s\n",
- SUMMARY_WIDTH, msg,
- pretty_peer, pretty_ref);
- }
- else {
- char quickref[83];
- char type = ' ';
- const char *msg = "";
-
- strcpy(quickref, find_unique_abbrev(ref->old_sha1, DEFAULT_ABBREV));
- if (ref_newer(ref->peer_ref->new_sha1, ref->old_sha1))
- strcat(quickref, "..");
- else {
- strcat(quickref, "...");
- type = '+';
- msg = " (forced update)";
- }
- strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-
- fprintf(stderr, " %c %-*s %s -> %s%s\n",
- type,
- SUMMARY_WIDTH, quickref,
- pretty_peer, pretty_ref,
- msg);
- }
+ ref->status = expect_status_report ?
+ REF_STATUS_EXPECTING_REPORT :
+ REF_STATUS_OK;
}
packet_flush(out);
- if (new_refs && !args.dry_run)
- ret = pack_objects(out, remote_refs);
- close(out);
-
- if (expect_status_report) {
- if (receive_status(in))
- ret = -4;
+ if (new_refs && !args.dry_run) {
+ if (pack_objects(out, remote_refs) < 0)
+ return -1;
}
+ else
+ close(out);
+
+ if (expect_status_report)
+ ret = receive_status(in, remote_refs);
+ else
+ ret = 0;
- if (!args.dry_run && remote && ret == 0) {
+ print_push_status(dest, remote_refs);
+
+ if (!args.dry_run && remote) {
for (ref = remote_refs; ref; ref = ref->next)
update_tracking_ref(remote, ref);
}
- if (!new_refs && ret == 0)
+ if (!refs_pushed(remote_refs))
fprintf(stderr, "Everything up-to-date\n");
- return ret;
+ if (ret < 0)
+ return ret;
+ for (ref = remote_refs; 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 void verify_remote_names(int nr_heads, const char **heads)
int i;
for (i = 0; i < nr_heads; i++) {
- const char *remote = strchr(heads[i], ':');
+ const char *remote = strrchr(heads[i], ':');
remote = remote ? (remote + 1) : heads[i];
switch (check_ref_format(remote)) {
case 0: /* ok */
- case -2: /* ok but a single level -- that is fine for
- * a match pattern.
- */
- case -3: /* ok but ends with a pattern-match character */
+ 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",
args.dry_run = 1;
continue;
}
+ if (!strcmp(arg, "--mirror")) {
+ args.send_mirror = 1;
+ continue;
+ }
if (!strcmp(arg, "--force")) {
args.force_update = 1;
continue;
}
if (!dest)
usage(send_pack_usage);
- if (heads && args.send_all)
+ /*
+ * --all and --mirror are incompatible; neither makes sense
+ * with any refspecs.
+ */
+ if ((heads && (args.send_all || args.send_mirror)) ||
+ (args.send_all && args.send_mirror))
usage(send_pack_usage);
if (remote_name) {
conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
close(fd[0]);
- close(fd[1]);
+ /* do_send_pack always closes fd[1] */
ret |= finish_connect(conn);
return !!ret;
}