diff options
Diffstat (limited to 'builtin/push.c')
-rw-r--r-- | builtin/push.c | 407 |
1 files changed, 337 insertions, 70 deletions
diff --git a/builtin/push.c b/builtin/push.c index 35cce532f2..57c138bd7b 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -11,15 +11,17 @@ #include "submodule.h" static const char * const push_usage[] = { - "git push [<options>] [<repository> [<refspec>...]]", + N_("git push [<options>] [<repository> [<refspec>...]]"), NULL, }; -static int thin; +static int thin = 1; static int deleterefs; static const char *receivepack; static int verbosity; -static int progress; +static int progress = -1; + +static struct push_cas_option cas; static const char **refspec; static int refspec_nr; @@ -32,51 +34,140 @@ static void add_refspec(const char *ref) refspec[refspec_nr-1] = ref; } -static void set_refspecs(const char **refs, int nr) +static const char *map_refspec(const char *ref, + struct remote *remote, struct ref *local_refs) +{ + struct ref *matched = NULL; + + /* Does "ref" uniquely name our ref? */ + if (count_refspec_match(ref, local_refs, &matched) != 1) + return ref; + + if (remote->push) { + struct refspec query; + memset(&query, 0, sizeof(struct refspec)); + query.src = matched->name; + if (!query_refspecs(remote->push, remote->push_refspec_nr, &query) && + query.dst) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%s%s:%s", + query.force ? "+" : "", + query.src, query.dst); + return strbuf_detach(&buf, NULL); + } + } + + if (push_default == PUSH_DEFAULT_UPSTREAM && + starts_with(matched->name, "refs/heads/")) { + struct branch *branch = branch_get(matched->name + 11); + if (branch->merge_nr == 1 && branch->merge[0]->src) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "%s:%s", + ref, branch->merge[0]->src); + return strbuf_detach(&buf, NULL); + } + } + + return ref; +} + +static void set_refspecs(const char **refs, int nr, const char *repo) { + struct remote *remote = NULL; + struct ref *local_refs = NULL; int i; + for (i = 0; i < nr; i++) { const char *ref = refs[i]; if (!strcmp("tag", ref)) { - char *tag; - int len; + struct strbuf tagref = STRBUF_INIT; if (nr <= ++i) die(_("tag shorthand without <tag>")); - len = strlen(refs[i]) + 11; - if (deleterefs) { - tag = xmalloc(len+1); - strcpy(tag, ":refs/tags/"); - } else { - tag = xmalloc(len); - strcpy(tag, "refs/tags/"); + ref = refs[i]; + if (deleterefs) + strbuf_addf(&tagref, ":refs/tags/%s", ref); + else + strbuf_addf(&tagref, "refs/tags/%s", ref); + ref = strbuf_detach(&tagref, NULL); + } else if (deleterefs) { + struct strbuf delref = STRBUF_INIT; + if (strchr(ref, ':')) + die(_("--delete only accepts plain target ref names")); + strbuf_addf(&delref, ":%s", ref); + ref = strbuf_detach(&delref, NULL); + } else if (!strchr(ref, ':')) { + if (!remote) { + /* lazily grab remote and local_refs */ + remote = remote_get(repo); + local_refs = get_local_heads(); } - 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")); + ref = map_refspec(ref, remote, local_refs); + } add_refspec(ref); } } -static void setup_push_upstream(struct remote *remote) +static int push_url_of_remote(struct remote *remote, const char ***url_p) +{ + if (remote->pushurl_nr) { + *url_p = remote->pushurl; + return remote->pushurl_nr; + } + *url_p = remote->url; + return remote->url_nr; +} + +static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { + /* + * There's no point in using shorten_unambiguous_ref here, + * as the ambiguity would be on the remote side, not what + * we have locally. Plus, this is supposed to be the simple + * mode. If the user is doing something crazy like setting + * upstream to a non-branch, we should probably be showing + * them the big ugly fully qualified ref. + */ + const char *advice_maybe = ""; + const char *short_upstream = branch->merge[0]->src; + + skip_prefix(short_upstream, "refs/heads/", &short_upstream); + + /* + * Don't show advice for people who explicitly set + * push.default. + */ + if (push_default == PUSH_DEFAULT_UNSPECIFIED) + advice_maybe = _("\n" + "To choose either option permanently, " + "see push.default in 'git help config'."); + die(_("The upstream branch of your current branch does not match\n" + "the name of your current branch. To push to the upstream branch\n" + "on the remote, use\n" + "\n" + " git push %s HEAD:%s\n" + "\n" + "To push to the branch of the same name on the remote, use\n" + "\n" + " git push %s %s\n" + "%s"), + remote->name, short_upstream, + remote->name, branch->name, advice_maybe); +} + +static const char message_detached_head_die[] = + N_("You are not currently on a branch.\n" + "To push the history leading to the current (detached HEAD)\n" + "state now, use\n" + "\n" + " git push %s HEAD:<name-of-remote-branch>\n"); + +static void setup_push_upstream(struct remote *remote, struct branch *branch, + int triangular, int simple) { struct strbuf refspec = STRBUF_INIT; - struct branch *branch = branch_get(NULL); + if (!branch) - die(_("You are not currently on a branch.\n" - "To push the history leading to the current (detached HEAD)\n" - "state now, use\n" - "\n" - " git push %s HEAD:<name-of-remote-branch>\n"), - remote->name); - if (!branch->merge_nr || !branch->merge) + die(_(message_detached_head_die), remote->name); + if (!branch->merge_nr || !branch->merge || !branch->remote_name) die(_("The current branch %s has no upstream branch.\n" "To push the current branch and set the remote as upstream, use\n" "\n" @@ -87,24 +178,94 @@ static void setup_push_upstream(struct remote *remote) if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); + if (triangular) + die(_("You are pushing to remote '%s', which is not the upstream of\n" + "your current branch '%s', without telling me what to push\n" + "to update which remote branch."), + remote->name, branch->name); + + if (simple) { + /* Additional safety */ + if (strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + } + strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } +static void setup_push_current(struct remote *remote, struct branch *branch) +{ + if (!branch) + die(_(message_detached_head_die), remote->name); + add_refspec(branch->name); +} + +static char warn_unspecified_push_default_msg[] = +N_("push.default is unset; its implicit value has changed in\n" + "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" + "and maintain the traditional behavior, use:\n" + "\n" + " git config --global push.default matching\n" + "\n" + "To squelch this message and adopt the new behavior now, use:\n" + "\n" + " git config --global push.default simple\n" + "\n" + "When push.default is set to 'matching', git will push local branches\n" + "to the remote branches that already exist with the same name.\n" + "\n" + "Since Git 2.0, Git defaults to the more conservative 'simple'\n" + "behavior, which only pushes the current branch to the corresponding\n" + "remote branch that 'git pull' uses to update the current branch.\n" + "\n" + "See 'git help config' and search for 'push.default' for further information.\n" + "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n" + "'current' instead of 'simple' if you sometimes use older versions of Git)"); + +static void warn_unspecified_push_default_configuration(void) +{ + static int warn_once; + + if (warn_once++) + return; + warning("%s\n", _(warn_unspecified_push_default_msg)); +} + +static int is_workflow_triangular(struct remote *remote) +{ + struct remote *fetch_remote = remote_get(NULL); + return (fetch_remote && fetch_remote != remote); +} + static void setup_default_push_refspecs(struct remote *remote) { + struct branch *branch = branch_get(NULL); + int triangular = is_workflow_triangular(remote); + switch (push_default) { default: case PUSH_DEFAULT_MATCHING: add_refspec(":"); break; + case PUSH_DEFAULT_UNSPECIFIED: + warn_unspecified_push_default_configuration(); + /* fallthru */ + + case PUSH_DEFAULT_SIMPLE: + if (triangular) + setup_push_current(remote, branch); + else + setup_push_upstream(remote, branch, triangular, 1); + break; + case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote); + setup_push_upstream(remote, branch, triangular, 0); break; case PUSH_DEFAULT_CURRENT: - add_refspec("HEAD"); + setup_push_current(remote, branch); break; case PUSH_DEFAULT_NOTHING: @@ -114,35 +275,108 @@ static void setup_default_push_refspecs(struct remote *remote) } } +static const char message_advice_pull_before_push[] = + N_("Updates were rejected because the tip of your current branch is behind\n" + "its remote counterpart. Integrate the remote changes (e.g.\n" + "'git pull ...') before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static const char message_advice_checkout_pull_push[] = + N_("Updates were rejected because a pushed branch tip is behind its remote\n" + "counterpart. Check out this branch and integrate the remote changes\n" + "(e.g. 'git pull ...') before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static const char message_advice_ref_fetch_first[] = + N_("Updates were rejected because the remote contains work that you do\n" + "not have locally. This is usually caused by another repository pushing\n" + "to the same ref. You may want to first integrate the remote changes\n" + "(e.g., 'git pull ...') before pushing again.\n" + "See the 'Note about fast-forwards' in 'git push --help' for details."); + +static const char message_advice_ref_already_exists[] = + N_("Updates were rejected because the tag already exists in the remote."); + +static const char message_advice_ref_needs_force[] = + N_("You cannot update a remote ref that points at a non-commit object,\n" + "or update a remote ref to make it point at a non-commit object,\n" + "without using the '--force' option.\n"); + +static void advise_pull_before_push(void) +{ + if (!advice_push_non_ff_current || !advice_push_update_rejected) + return; + advise(_(message_advice_pull_before_push)); +} + +static void advise_checkout_pull_push(void) +{ + if (!advice_push_non_ff_matching || !advice_push_update_rejected) + return; + advise(_(message_advice_checkout_pull_push)); +} + +static void advise_ref_already_exists(void) +{ + if (!advice_push_already_exists || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_already_exists)); +} + +static void advise_ref_fetch_first(void) +{ + if (!advice_push_fetch_first || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_fetch_first)); +} + +static void advise_ref_needs_force(void) +{ + if (!advice_push_needs_force || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_needs_force)); +} + static int push_with_options(struct transport *transport, int flags) { int err; - int nonfastforward; + unsigned int reject_reasons; transport_set_verbosity(transport, verbosity, progress); if (receivepack) transport_set_option(transport, TRANS_OPT_RECEIVEPACK, receivepack); - if (thin) - transport_set_option(transport, TRANS_OPT_THIN, "yes"); + transport_set_option(transport, TRANS_OPT_THIN, thin ? "yes" : NULL); + + if (!is_empty_cas(&cas)) { + if (!transport->smart_options) + die("underlying transport does not support --%s option", + CAS_OPT_NAME); + transport->smart_options->cas = &cas; + } if (verbosity > 0) fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, - &nonfastforward); + &reject_reasons); 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) { - fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n" - "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n" - "'Note about fast-forwards' section of 'git push --help' for details.\n")); + if (reject_reasons & REJECT_NON_FF_HEAD) { + advise_pull_before_push(); + } else if (reject_reasons & REJECT_NON_FF_OTHER) { + advise_checkout_pull_push(); + } else if (reject_reasons & REJECT_ALREADY_EXISTS) { + advise_ref_already_exists(); + } else if (reject_reasons & REJECT_FETCH_FIRST) { + advise_ref_fetch_first(); + } else if (reject_reasons & REJECT_NEEDS_FORCE) { + advise_ref_needs_force(); } return 1; @@ -151,7 +385,7 @@ static int push_with_options(struct transport *transport, int flags) static int do_push(const char *repo, int flags) { int i, errs; - struct remote *remote = remote_get(repo); + struct remote *remote = pushremote_get(repo); const char **url; int url_nr; @@ -196,13 +430,7 @@ static int do_push(const char *repo, int flags) setup_default_push_refspecs(remote); } errs = 0; - if (remote->pushurl_nr) { - url = remote->pushurl; - url_nr = remote->pushurl_nr; - } else { - url = remote->url; - url_nr = remote->url_nr; - } + url_nr = push_url_of_remote(remote, &url); if (url_nr) { for (i = 0; i < url_nr; i++) { struct transport *transport = @@ -224,17 +452,45 @@ static int option_parse_recurse_submodules(const struct option *opt, const char *arg, int unset) { int *flags = opt->value; + + if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK | + TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND)) + die("%s can only be used once.", opt->long_name); + if (arg) { if (!strcmp(arg, "check")) *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else if (!strcmp(arg, "on-demand")) + *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; else die("bad %s argument: %s", opt->long_name, arg); } else - die("option %s needs an argument (check)", opt->long_name); + die("option %s needs an argument (check|on-demand)", + opt->long_name); return 0; } +static int git_push_config(const char *k, const char *v, void *cb) +{ + int *flags = cb; + int status; + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + + if (!strcmp(k, "push.followtags")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FOLLOW_TAGS; + else + *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; + return 0; + } + + return git_default_config(k, v, NULL); +} + int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; @@ -243,29 +499,40 @@ int cmd_push(int argc, const char **argv, const char *prefix) const char *repo = NULL; /* default repository */ struct option options[] = { OPT__VERBOSITY(&verbosity), - 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", + OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")), + OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL), + OPT_BIT( 0 , "mirror", &flags, N_("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), - { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check", - "controls recursive pushing of submodules", + OPT_BOOL( 0, "delete", &deleterefs, N_("delete refs")), + OPT_BOOL( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), + OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), + OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), + OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), + { OPTION_CALLBACK, + 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG, parseopt_push_cas_option }, + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check|on-demand", + N_("control recursive pushing of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, - 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", + OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")), + OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), + OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")), + OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"), TRANSPORT_PUSH_SET_UPSTREAM), - OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), + OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), + OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"), + TRANSPORT_PUSH_PRUNE), + OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), + OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), + TRANSPORT_PUSH_FOLLOW_TAGS), + OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), + OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), OPT_END() }; packet_trace_identity("push"); - git_config(git_default_config, NULL); + git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) @@ -278,7 +545,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) if (argc > 0) { repo = argv[0]; - set_refspecs(argv + 1, argc - 1); + set_refspecs(argv + 1, argc - 1, repo); } rc = do_push(repo, flags); |