From: Junio C Hamano Date: Mon, 15 Mar 2010 07:58:24 +0000 (-0700) Subject: Merge branch 'ld/push-porcelain' X-Git-Tag: v1.7.1-rc0~67 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=66bce02ec459123d07c3f0230906cc2f8e36504c;p=git.git Merge branch 'ld/push-porcelain' * ld/push-porcelain: t5516: Use test_cmp when appropriate git-push: add tests for git push --porcelain git-push: make git push --porcelain print "Done" git-push: send "To " messages to the standard output in --porcelain mode git-push: fix an advice message so it goes to stderr Conflicts: transport.c --- 66bce02ec459123d07c3f0230906cc2f8e36504c diff --cc builtin/push.c index f7bc2b292,000000000..235ca1245 mode 100644,000000..100644 --- a/builtin/push.c +++ b/builtin/push.c @@@ -1,247 -1,0 +1,247 @@@ +/* + * "git push" + */ +#include "cache.h" +#include "refs.h" +#include "run-command.h" +#include "builtin.h" +#include "remote.h" +#include "transport.h" +#include "parse-options.h" + +static const char * const push_usage[] = { + "git push [] [ ...]", + NULL, +}; + +static int thin; +static int deleterefs; +static const char *receivepack; + +static const char **refspec; +static int refspec_nr; + +static void add_refspec(const char *ref) +{ + int nr = refspec_nr + 1; + refspec = xrealloc(refspec, nr * sizeof(char *)); + refspec[nr-1] = ref; + refspec_nr = nr; +} + +static void set_refspecs(const char **refs, int nr) +{ + int i; + for (i = 0; i < nr; i++) { + const char *ref = refs[i]; + if (!strcmp("tag", ref)) { + char *tag; + int len; + if (nr <= ++i) + die("tag shorthand without "); + len = strlen(refs[i]) + 11; + if (deleterefs) { + tag = xmalloc(len+1); + strcpy(tag, ":refs/tags/"); + } else { + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + } + strcat(tag, refs[i]); + ref = tag; + } else if (deleterefs && !strchr(ref, ':')) { + char *delref; + int len = strlen(ref)+1; + delref = xmalloc(len+1); + strcpy(delref, ":"); + strcat(delref, ref); + ref = delref; + } else if (deleterefs) + die("--delete only accepts plain target ref names"); + add_refspec(ref); + } +} + +static void setup_push_tracking(void) +{ + struct strbuf refspec = STRBUF_INIT; + struct branch *branch = branch_get(NULL); + if (!branch) + die("You are not currently on a branch."); + if (!branch->merge_nr || !branch->merge) + die("The current branch %s is not tracking anything.", + branch->name); + if (branch->merge_nr != 1) + die("The current branch %s is tracking multiple branches, " + "refusing to push.", branch->name); + strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); + add_refspec(refspec.buf); +} + +static void setup_default_push_refspecs(void) +{ + switch (push_default) { + default: + case PUSH_DEFAULT_MATCHING: + add_refspec(":"); + break; + + case PUSH_DEFAULT_TRACKING: + setup_push_tracking(); + break; + + case PUSH_DEFAULT_CURRENT: + add_refspec("HEAD"); + break; + + case PUSH_DEFAULT_NOTHING: + die("You didn't specify any refspecs to push, and " + "push.default is \"nothing\"."); + break; + } +} + +static int push_with_options(struct transport *transport, int flags) +{ + int err; + int nonfastforward; + if (receivepack) + transport_set_option(transport, + TRANS_OPT_RECEIVEPACK, receivepack); + if (thin) + transport_set_option(transport, TRANS_OPT_THIN, "yes"); + + if (flags & TRANSPORT_PUSH_VERBOSE) + fprintf(stderr, "Pushing to %s\n", transport->url); + err = transport_push(transport, refspec_nr, refspec, flags, + &nonfastforward); + if (err != 0) + error("failed to push some refs to '%s'", transport->url); + + err |= transport_disconnect(transport); + + if (!err) + return 0; + + if (nonfastforward && advice_push_nonfastforward) { - printf("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes before pushing again. See the 'Note about\n" - "fast-forwards' section of 'git push --help' for details.\n"); ++ fprintf(stderr, "To prevent you from losing history, non-fast-forward updates were rejected\n" ++ "Merge the remote changes before pushing again. See the 'Note about\n" ++ "fast-forwards' section of 'git push --help' for details.\n"); + } + + return 1; +} + +static int do_push(const char *repo, int flags) +{ + int i, errs; + struct remote *remote = remote_get(repo); + const char **url; + int url_nr; + + if (!remote) { + if (repo) + die("bad repository '%s'", repo); + die("No destination configured to push to."); + } + + if (remote->mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if ((flags & TRANSPORT_PUSH_ALL) && refspec) { + if (!strcmp(*refspec, "refs/tags/*")) + return error("--all and --tags are incompatible"); + return error("--all can't be combined with refspecs"); + } + + if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { + if (!strcmp(*refspec, "refs/tags/*")) + return error("--mirror and --tags are incompatible"); + return error("--mirror can't be combined with refspecs"); + } + + if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == + (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { + return error("--all and --mirror are incompatible"); + } + + if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { + if (remote->push_refspec_nr) { + refspec = remote->push_refspec; + refspec_nr = remote->push_refspec_nr; + } else if (!(flags & TRANSPORT_PUSH_MIRROR)) + setup_default_push_refspecs(); + } + errs = 0; + if (remote->pushurl_nr) { + url = remote->pushurl; + url_nr = remote->pushurl_nr; + } else { + url = remote->url; + url_nr = remote->url_nr; + } + if (url_nr) { + for (i = 0; i < url_nr; i++) { + struct transport *transport = + transport_get(remote, url[i]); + if (push_with_options(transport, flags)) + errs++; + } + } else { + struct transport *transport = + transport_get(remote, NULL); + + if (push_with_options(transport, flags)) + errs++; + } + return !!errs; +} + +int cmd_push(int argc, const char **argv, const char *prefix) +{ + int flags = 0; + int tags = 0; + int rc; + const char *repo = NULL; /* default repository */ + struct option options[] = { + OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), + OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), + OPT_STRING( 0 , "repo", &repo, "repository", "repository"), + OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), + OPT_BIT( 0 , "mirror", &flags, "mirror all refs", + (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), + OPT_BOOLEAN( 0, "delete", &deleterefs, "delete refs"), + OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"), + OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), + OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), + OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), + OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), + OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), + OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), + OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", + TRANSPORT_PUSH_SET_UPSTREAM), + OPT_END() + }; + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, options, push_usage, 0); + + if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) + die("--delete is incompatible with --all, --mirror and --tags"); + if (deleterefs && argc < 2) + die("--delete doesn't make sense without any refs"); + + if (tags) + add_refspec("refs/tags/*"); + + if (argc > 0) { + repo = argv[0]; + set_refspecs(argv + 1, argc - 1); + } + + rc = do_push(repo, flags); + if (rc == -1) + usage_with_options(push_usage, options); + else + return rc; +} diff --cc builtin/send-pack.c index 6019eac91,000000000..481602d8a mode 100644,000000..100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@@ -1,527 -1,0 +1,531 @@@ +#include "cache.h" +#include "commit.h" +#include "refs.h" +#include "pkt-line.h" +#include "sideband.h" +#include "run-command.h" +#include "remote.h" +#include "send-pack.h" +#include "quote.h" +#include "transport.h" + +static const char send_pack_usage[] = +"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" +" --all and explicit specification are mutually exclusive."; + +static struct send_pack_args args; + +static int feed_object(const unsigned char *sha1, int fd, int negative) +{ + char buf[42]; + + if (negative && !has_sha1_file(sha1)) + return 1; + + memcpy(buf + negative, sha1_to_hex(sha1), 40); + if (negative) + buf[0] = '^'; + buf[40 + negative] = '\n'; + return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs"); +} + +/* + * Make a pack stream and spit it out into file descriptor fd + */ +static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args) +{ + /* + * The child becomes pack-objects --revs; we feed + * the revision parameters to it via its stdin and + * let its stdout go back to the other end. + */ + const char *argv[] = { + "pack-objects", + "--all-progress-implied", + "--revs", + "--stdout", + NULL, + NULL, + NULL, + NULL, + }; + struct child_process po; + int i; + + i = 4; + if (args->use_thin_pack) + argv[i++] = "--thin"; + if (args->use_ofs_delta) + argv[i++] = "--delta-base-offset"; + if (args->quiet) + argv[i++] = "-q"; + memset(&po, 0, sizeof(po)); + po.argv = argv; + po.in = -1; + po.out = args->stateless_rpc ? -1 : fd; + po.git_cmd = 1; + if (start_command(&po)) + die_errno("git pack-objects failed"); + + /* + * We feed the pack-objects we just spawned with revision + * parameters by writing to the pipe. + */ + for (i = 0; i < extra->nr; i++) + if (!feed_object(extra->array[i], po.in, 1)) + break; + + while (refs) { + if (!is_null_sha1(refs->old_sha1) && + !feed_object(refs->old_sha1, po.in, 1)) + break; + if (!is_null_sha1(refs->new_sha1) && + !feed_object(refs->new_sha1, po.in, 0)) + break; + refs = refs->next; + } + + close(po.in); + + if (args->stateless_rpc) { + char *buf = xmalloc(LARGE_PACKET_MAX); + while (1) { + ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); + if (n <= 0) + break; + send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); + } + free(buf); + close(po.out); + po.out = -1; + } + + if (finish_command(&po)) + return error("pack-objects died with strange error"); + return 0; +} + +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)) + return error("did not receive remote status"); + if (memcmp(line, "unpack ok\n", 10)) { + 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 ", 3) && memcmp(line, "ng ", 3))) { + fprintf(stderr, "protocol error: %s\n", line); + ret = -1; + break; + } + + 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; + } + + 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 print_helper_status(struct ref *ref) +{ + struct strbuf buf = STRBUF_INIT; + + for (; ref; ref = ref->next) { + const char *msg = NULL; + const char *res; + + switch(ref->status) { + case REF_STATUS_NONE: + res = "error"; + msg = "no match"; + break; + + case REF_STATUS_OK: + res = "ok"; + break; + + case REF_STATUS_UPTODATE: + res = "ok"; + msg = "up to date"; + break; + + case REF_STATUS_REJECT_NONFASTFORWARD: + res = "error"; + msg = "non-fast forward"; + break; + + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_REMOTE_REJECT: + res = "error"; + break; + + case REF_STATUS_EXPECTING_REPORT: + default: + continue; + } + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s %s", res, ref->name); + if (ref->remote_status) + msg = ref->remote_status; + if (msg) { + strbuf_addch(&buf, ' '); + quote_two_c_style(&buf, "", msg, 0); + } + strbuf_addch(&buf, '\n'); + + safe_write(1, buf.buf, buf.len); + } + strbuf_release(&buf); +} + +static int sideband_demux(int in, int out, void *data) +{ + int *fd = data; + int ret = recv_sideband("send-pack", fd[0], out); + close(out); + return ret; +} + +int send_pack(struct send_pack_args *args, + int fd[], struct child_process *conn, + struct ref *remote_refs, + struct extra_have_objects *extra_have) +{ + int in = fd[0]; + int out = fd[1]; + struct strbuf req_buf = STRBUF_INIT; + struct ref *ref; + int new_refs; + int allow_deleting_refs = 0; + int status_report = 0; + int use_sideband = 0; + unsigned cmds_sent = 0; + int ret; + struct async demux; + + /* Does the other end support the reporting? */ + if (server_supports("report-status")) + status_report = 1; + if (server_supports("delete-refs")) + allow_deleting_refs = 1; + if (server_supports("ofs-delta")) + args->use_ofs_delta = 1; + if (server_supports("side-band-64k")) + use_sideband = 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"); + return 0; + } + + /* + * Finally, tell the other end! + */ + new_refs = 0; + for (ref = remote_refs; ref; ref = ref->next) { + if (!ref->peer_ref && !args->send_mirror) + continue; + + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: + continue; + default: + ; /* do nothing */ + } + + if (ref->deletion && !allow_deleting_refs) { + ref->status = REF_STATUS_REJECT_NODELETE; + continue; + } + + if (!ref->deletion) + new_refs++; + + if (args->dry_run) { + ref->status = REF_STATUS_OK; + } else { + char *old_hex = sha1_to_hex(ref->old_sha1); + char *new_hex = sha1_to_hex(ref->new_sha1); + + if (!cmds_sent && (status_report || use_sideband)) { + packet_buf_write(&req_buf, "%s %s %s%c%s%s", + old_hex, new_hex, ref->name, 0, + status_report ? " report-status" : "", + use_sideband ? " side-band-64k" : ""); + } + else + packet_buf_write(&req_buf, "%s %s %s", + old_hex, new_hex, ref->name); + ref->status = status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + cmds_sent++; + } + } + + if (args->stateless_rpc) { + if (!args->dry_run && cmds_sent) { + packet_buf_flush(&req_buf); + send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); + } + } else { + safe_write(out, req_buf.buf, req_buf.len); + packet_flush(out); + } + strbuf_release(&req_buf); + + if (use_sideband && cmds_sent) { + memset(&demux, 0, sizeof(demux)); + demux.proc = sideband_demux; + demux.data = fd; + demux.out = -1; + if (start_async(&demux)) + die("receive-pack: unable to fork off sideband demultiplexer"); + in = demux.out; + } + + if (new_refs && cmds_sent) { + if (pack_objects(out, remote_refs, extra_have, args) < 0) { + for (ref = remote_refs; ref; ref = ref->next) + ref->status = REF_STATUS_NONE; + if (use_sideband) + finish_async(&demux); + return -1; + } + } + if (args->stateless_rpc && cmds_sent) + packet_flush(out); + + if (status_report && cmds_sent) + ret = receive_status(in, remote_refs); + else + ret = 0; + if (args->stateless_rpc) + packet_flush(out); + + if (use_sideband && cmds_sent) { + if (finish_async(&demux)) { + error("error in sideband demultiplexer"); + ret = -1; + } + close(demux.out); + } + + if (ret < 0) + return ret; ++ ++ if (args->porcelain) ++ return 0; ++ + 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; +} + +int cmd_send_pack(int argc, const char **argv, const char *prefix) +{ + int i, nr_refspecs = 0; + const char **refspecs = NULL; + const char *remote_name = NULL; + struct remote *remote = NULL; + const char *dest = NULL; + int fd[2]; + struct child_process *conn; + struct extra_have_objects extra_have; + struct ref *remote_refs, *local_refs; + int ret; + int helper_status = 0; + int send_all = 0; + const char *receivepack = "git-receive-pack"; + int flags; + int nonfastforward = 0; + + argv++; + for (i = 1; i < argc; i++, argv++) { + const char *arg = *argv; + + if (*arg == '-') { + if (!prefixcmp(arg, "--receive-pack=")) { + receivepack = arg + 15; + continue; + } + if (!prefixcmp(arg, "--exec=")) { + receivepack = arg + 7; + continue; + } + if (!prefixcmp(arg, "--remote=")) { + remote_name = arg + 9; + continue; + } + if (!strcmp(arg, "--all")) { + send_all = 1; + continue; + } + if (!strcmp(arg, "--dry-run")) { + args.dry_run = 1; + continue; + } + if (!strcmp(arg, "--mirror")) { + args.send_mirror = 1; + continue; + } + if (!strcmp(arg, "--force")) { + args.force_update = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + args.verbose = 1; + continue; + } + if (!strcmp(arg, "--thin")) { + args.use_thin_pack = 1; + continue; + } + if (!strcmp(arg, "--stateless-rpc")) { + args.stateless_rpc = 1; + continue; + } + if (!strcmp(arg, "--helper-status")) { + helper_status = 1; + continue; + } + usage(send_pack_usage); + } + if (!dest) { + dest = arg; + continue; + } + refspecs = (const char **) argv; + nr_refspecs = argc - i; + break; + } + if (!dest) + usage(send_pack_usage); + /* + * --all and --mirror are incompatible; neither makes sense + * with any refspecs. + */ + if ((refspecs && (send_all || args.send_mirror)) || + (send_all && args.send_mirror)) + usage(send_pack_usage); + + if (remote_name) { + remote = remote_get(remote_name); + if (!remote_has_url(remote, dest)) { + die("Destination %s is not a uri for %s", + dest, remote_name); + } + } + + if (args.stateless_rpc) { + conn = NULL; + fd[0] = 0; + fd[1] = 1; + } else { + conn = git_connect(fd, dest, receivepack, + args.verbose ? CONNECT_VERBOSE : 0); + } + + memset(&extra_have, 0, sizeof(extra_have)); + + get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, + &extra_have); + + transport_verify_remote_names(nr_refspecs, refspecs); + + local_refs = get_local_heads(); + + flags = MATCH_REFS_NONE; + + if (send_all) + flags |= MATCH_REFS_ALL; + if (args.send_mirror) + flags |= MATCH_REFS_MIRROR; + + /* match them up */ + if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) + return -1; + + set_ref_status_for_push(remote_refs, args.send_mirror, + args.force_update); + + ret = send_pack(&args, fd, conn, remote_refs, &extra_have); + + if (helper_status) + print_helper_status(remote_refs); + + close(fd[1]); + close(fd[0]); + + ret |= finish_connect(conn); + + if (!helper_status) + transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward); + + if (!args.dry_run && remote) { + struct ref *ref; + for (ref = remote_refs; ref; ref = ref->next) + transport_update_tracking_ref(remote, ref, args.verbose); + } + + if (!ret && !transport_refs_pushed(remote_refs)) + fprintf(stderr, "Everything up-to-date\n"); + + return ret; +} diff --cc transport.c index f07bd33e8,260350b5a..825be9456 --- a/transport.c +++ b/transport.c @@@ -1065,13 -1052,12 +1066,12 @@@ int transport_push(struct transport *tr flags & TRANSPORT_PUSH_MIRROR, flags & TRANSPORT_PUSH_FORCE); - ret = transport->push_refs(transport, remote_refs, flags); + push_ret = transport->push_refs(transport, remote_refs, flags); err = push_had_errors(remote_refs); - - ret |= err; + ret = push_ret | err; if (!quiet || err) - print_push_status(transport->url, remote_refs, + transport_print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); @@@ -1081,11 -1067,14 +1081,14 @@@ 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 && !transport_refs_pushed(remote_refs)) + if (porcelain && !push_ret) + puts("Done"); - else if (!quiet && !ret && !refs_pushed(remote_refs)) ++ else if (!quiet && !ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); + return ret; } return 1;