diff options
Diffstat (limited to 'builtin/push.c')
-rw-r--r-- | builtin/push.c | 628 |
1 files changed, 429 insertions, 199 deletions
diff --git a/builtin/push.c b/builtin/push.c index db9ba30b08..4b026ce6c6 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -2,67 +2,131 @@ * "git push" */ #include "cache.h" +#include "config.h" #include "refs.h" +#include "refspec.h" #include "run-command.h" #include "builtin.h" #include "remote.h" #include "transport.h" #include "parse-options.h" #include "submodule.h" +#include "submodule-config.h" +#include "send-pack.h" +#include "color.h" static const char * const push_usage[] = { N_("git push [<options>] [<repository> [<refspec>...]]"), NULL, }; -static int thin; +static int push_use_color = -1; +static char push_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_RED, /* ERROR */ +}; + +enum color_push { + PUSH_COLOR_RESET = 0, + PUSH_COLOR_ERROR = 1 +}; + +static int parse_push_color_slot(const char *slot) +{ + if (!strcasecmp(slot, "reset")) + return PUSH_COLOR_RESET; + if (!strcasecmp(slot, "error")) + return PUSH_COLOR_ERROR; + return -1; +} + +static const char *push_get_color(enum color_push ix) +{ + if (want_color_stderr(push_use_color)) + return push_colors[ix]; + return ""; +} + +static int thin = 1; static int deleterefs; static const char *receivepack; static int verbosity; static int progress = -1; +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static enum transport_family family; -static const char **refspec; -static int refspec_nr; -static int refspec_alloc; -static int default_matching_used; +static struct push_cas_option cas; -static void add_refspec(const char *ref) +static struct refspec rs = REFSPEC_INIT_PUSH; + +static struct string_list push_options_config = STRING_LIST_INIT_DUP; + +static void refspec_append_mapped(struct refspec *refspec, const char *ref, + struct remote *remote, struct ref *local_refs) { - refspec_nr++; - ALLOC_GROW(refspec, refspec_nr, refspec_alloc); - refspec[refspec_nr-1] = ref; + const char *branch_name; + struct ref *matched = NULL; + + /* Does "ref" uniquely name our ref? */ + if (count_refspec_match(ref, local_refs, &matched) != 1) { + refspec_append(refspec, ref); + return; + } + + if (remote->push.nr) { + struct refspec_item query; + memset(&query, 0, sizeof(struct refspec_item)); + query.src = matched->name; + if (!query_refspecs(&remote->push, &query) && query.dst) { + refspec_appendf(refspec, "%s%s:%s", + query.force ? "+" : "", + query.src, query.dst); + return; + } + } + + if (push_default == PUSH_DEFAULT_UPSTREAM && + skip_prefix(matched->name, "refs/heads/", &branch_name)) { + struct branch *branch = branch_get(branch_name); + if (branch->merge_nr == 1 && branch->merge[0]->src) { + refspec_appendf(refspec, "%s:%s", + ref, branch->merge[0]->src); + return; + } + } + + refspec_append(refspec, ref); } -static void set_refspecs(const char **refs, int nr) +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; 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) + refspec_appendf(&rs, ":refs/tags/%s", ref); + else + refspec_appendf(&rs, "refs/tags/%s", ref); + } else if (deleterefs) { + if (strchr(ref, ':') || !*ref) + die(_("--delete only accepts plain target ref names")); + refspec_appendf(&rs, ":%s", ref); + } 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")); - add_refspec(ref); + refspec_append_mapped(&rs, ref, remote, local_refs); + } else + refspec_append(&rs, ref); } } @@ -76,7 +140,9 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { +static NORETURN void 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 @@ -86,13 +152,12 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote * them the big ugly fully qualified ref. */ const char *advice_maybe = ""; - const char *short_upstream = - skip_prefix(branch->merge[0]->src, "refs/heads/"); + const char *short_upstream = branch->merge[0]->src; + + skip_prefix(short_upstream, "refs/heads/", &short_upstream); - if (!short_upstream) - short_upstream = branch->merge[0]->src; /* - * Don't show advice for people who explicitely set + * Don't show advice for people who explicitly set * push.default. */ if (push_default == PUSH_DEFAULT_UNSPECIFIED) @@ -107,225 +172,234 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote "\n" "To push to the branch of the same name on the remote, use\n" "\n" - " git push %s %s\n" + " git push %s HEAD\n" "%s"), remote->name, short_upstream, - remote->name, branch->name, advice_maybe); + remote->name, advice_maybe); } -static void setup_push_upstream(struct remote *remote, int simple) +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 const char *get_upstream_ref(struct branch *branch, const char *remote_name) { - 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 || !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" " git push --set-upstream %s %s\n"), branch->name, - remote->name, + remote_name, branch->name); if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); - if (strcmp(branch->remote_name, remote->name)) - 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 && 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 char warn_unspecified_push_default_msg[] = -N_("push.default is unset; its implicit value is changing in\n" - "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" - "and maintain the current behavior after the default changes, 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" - "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)); + return branch->merge[0]->src; } static void setup_default_push_refspecs(struct remote *remote) { + struct branch *branch; + const char *dst; + int same_remote; + switch (push_default) { - default: - case PUSH_DEFAULT_UNSPECIFIED: - default_matching_used = 1; - warn_unspecified_push_default_configuration(); - /* fallthru */ case PUSH_DEFAULT_MATCHING: - add_refspec(":"); + refspec_append(&rs, ":"); + return; + + case PUSH_DEFAULT_NOTHING: + die(_("You didn't specify any refspecs to push, and " + "push.default is \"nothing\".")); + return; + default: break; + } + + branch = branch_get(NULL); + if (!branch) + die(_(message_detached_head_die), remote->name); + + dst = branch->refname; + same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL)); + switch (push_default) { + default: + case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_SIMPLE: - setup_push_upstream(remote, 1); + if (!same_remote) + break; + if (strcmp(branch->refname, get_upstream_ref(branch, remote->name))) + die_push_simple(branch, remote); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, 0); + if (!same_remote) + 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); + dst = get_upstream_ref(branch, remote->name); 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; } + + refspec_appendf(&rs, "%s:%s", branch->refname, dst); } 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. Merge the remote changes (e.g. 'git pull')\n" - "before pushing again.\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_use_upstream[] = - N_("Updates were rejected because a pushed branch tip is behind its remote\n" - "counterpart. If you did not intend to push that branch, you may want to\n" - "specify branches to push or set the 'push.default' configuration variable\n" - "to 'simple', 'current' or 'upstream' to push only the current branch."); - 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 merge the remote changes\n" - "(e.g. 'git pull') before pushing again.\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 const char message_advice_ref_needs_update[] = + N_("Updates were rejected because the tip of the remote-tracking\n" + "branch has been updated since the last checkout. You may want\n" + "to integrate those changes locally (e.g., 'git pull ...')\n" + "before forcing an update.\n"); + static void advise_pull_before_push(void) { - if (!advice_push_non_ff_current || !advice_push_nonfastforward) + if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; advise(_(message_advice_pull_before_push)); } -static void advise_use_upstream(void) +static void advise_checkout_pull_push(void) { - if (!advice_push_non_ff_default || !advice_push_nonfastforward) + if (!advice_enabled(ADVICE_PUSH_NON_FF_MATCHING) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; - advise(_(message_advice_use_upstream)); + advise(_(message_advice_checkout_pull_push)); } -static void advise_checkout_pull_push(void) +static void advise_ref_already_exists(void) { - if (!advice_push_non_ff_matching || !advice_push_nonfastforward) + if (!advice_enabled(ADVICE_PUSH_ALREADY_EXISTS) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) return; - advise(_(message_advice_checkout_pull_push)); + advise(_(message_advice_ref_already_exists)); +} + +static void advise_ref_fetch_first(void) +{ + if (!advice_enabled(ADVICE_PUSH_FETCH_FIRST) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) + return; + advise(_(message_advice_ref_fetch_first)); +} + +static void advise_ref_needs_force(void) +{ + if (!advice_enabled(ADVICE_PUSH_NEEDS_FORCE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) + return; + advise(_(message_advice_ref_needs_force)); +} + +static void advise_ref_needs_update(void) +{ + if (!advice_enabled(ADVICE_PUSH_REF_NEEDS_UPDATE) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED)) + return; + advise(_(message_advice_ref_needs_update)); } -static int push_with_options(struct transport *transport, int flags) +static int push_with_options(struct transport *transport, struct refspec *rs, + int flags) { int err; - int nonfastforward; + unsigned int reject_reasons; + char *anon_url = transport_anonymize_url(transport->url); transport_set_verbosity(transport, verbosity, progress); + transport->family = family; 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); - if (err != 0) - error(_("failed to push some refs to '%s'"), transport->url); + fprintf(stderr, _("Pushing to %s\n"), anon_url); + trace2_region_enter("push", "transport_push", the_repository); + err = transport_push(the_repository, transport, + rs, flags, &reject_reasons); + trace2_region_leave("push", "transport_push", the_repository); + if (err != 0) { + fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR)); + error(_("failed to push some refs to '%s'"), anon_url); + fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET)); + } err |= transport_disconnect(transport); + free(anon_url); if (!err) return 0; - switch (nonfastforward) { - default: - break; - case NON_FF_HEAD: + if (reject_reasons & REJECT_NON_FF_HEAD) { advise_pull_before_push(); - break; - case NON_FF_OTHER: - if (default_matching_used) - advise_use_upstream(); - else - advise_checkout_pull_push(); - break; + } 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(); + } else if (reject_reasons & REJECT_REF_NEEDS_UPDATE) { + advise_ref_needs_update(); } return 1; } -static int do_push(const char *repo, int flags) +static int do_push(int flags, + const struct string_list *push_options, + struct remote *remote) { int i, errs; - struct remote *remote = remote_get(repo); const char **url; int url_nr; + struct refspec *push_refspec = &rs; - if (!remote) { - if (repo) - die(_("bad repository '%s'"), repo); - die(_("No configured push destination.\n" - "Either specify the URL from the command-line or configure a remote repository using\n" - "\n" - " git remote add <name> <url>\n" - "\n" - "and then push using the remote name\n" - "\n" - " git push <name>\n")); - } + if (push_options->nr) + flags |= TRANSPORT_PUSH_OPTIONS; - 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; + if (!push_refspec->nr && !(flags & TRANSPORT_PUSH_ALL)) { + if (remote->push.nr) { + push_refspec = &remote->push; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) setup_default_push_refspecs(remote); } @@ -335,14 +409,17 @@ static int do_push(const char *repo, int flags) for (i = 0; i < url_nr; i++) { struct transport *transport = transport_get(remote, url[i]); - if (push_with_options(transport, flags)) + if (flags & TRANSPORT_PUSH_OPTIONS) + transport->push_options = push_options; + if (push_with_options(transport, push_refspec, flags)) errs++; } } else { struct transport *transport = transport_get(remote, NULL); - - if (push_with_options(transport, flags)) + if (flags & TRANSPORT_PUSH_OPTIONS) + transport->push_options = push_options; + if (push_with_options(transport, push_refspec, flags)) errs++; } return !!errs; @@ -351,47 +428,137 @@ static int do_push(const char *repo, int flags) static int option_parse_recurse_submodules(const struct option *opt, const char *arg, int unset) { - int *flags = opt->value; + int *recurse_submodules = opt->value; + + if (unset) + *recurse_submodules = RECURSE_SUBMODULES_OFF; + else + *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); + + return 0; +} + +static void set_push_cert_flags(int *flags, int v) +{ + switch (v) { + case SEND_PACK_PUSH_CERT_NEVER: + *flags &= ~(TRANSPORT_PUSH_CERT_ALWAYS | TRANSPORT_PUSH_CERT_IF_ASKED); + break; + case SEND_PACK_PUSH_CERT_ALWAYS: + *flags |= TRANSPORT_PUSH_CERT_ALWAYS; + *flags &= ~TRANSPORT_PUSH_CERT_IF_ASKED; + break; + case SEND_PACK_PUSH_CERT_IF_ASKED: + *flags |= TRANSPORT_PUSH_CERT_IF_ASKED; + *flags &= ~TRANSPORT_PUSH_CERT_ALWAYS; + break; + } +} + + +static int git_push_config(const char *k, const char *v, void *cb) +{ + const char *slot_name; + int *flags = cb; + int status; - if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK | - TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND)) - die("%s can only be used once.", opt->long_name); + status = git_gpg_config(k, v, NULL); + if (status) + return status; - if (arg) { - if (!strcmp(arg, "check")) - *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; - else if (!strcmp(arg, "on-demand")) - *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + if (!strcmp(k, "push.followtags")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FOLLOW_TAGS; + else + *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; + return 0; + } else if (!strcmp(k, "push.gpgsign")) { + const char *value; + if (!git_config_get_value("push.gpgsign", &value)) { + switch (git_parse_maybe_bool(value)) { + case 0: + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER); + break; + case 1: + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_ALWAYS); + break; + default: + if (value && !strcasecmp(value, "if-asked")) + set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED); + else + return error("Invalid value for '%s'", k); + } + } + } else if (!strcmp(k, "push.recursesubmodules")) { + const char *value; + if (!git_config_get_value("push.recursesubmodules", &value)) + recurse_submodules = parse_push_recurse_submodules_arg(k, value); + } else if (!strcmp(k, "submodule.recurse")) { + int val = git_config_bool(k, v) ? + RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF; + recurse_submodules = val; + } else if (!strcmp(k, "push.pushoption")) { + if (!v) + return config_error_nonbool(k); else - die("bad %s argument: %s", opt->long_name, arg); - } else - die("option %s needs an argument (check|on-demand)", - opt->long_name); + if (!*v) + string_list_clear(&push_options_config, 0); + else + string_list_append(&push_options_config, v); + return 0; + } else if (!strcmp(k, "color.push")) { + push_use_color = git_config_colorbool(k, v); + return 0; + } else if (skip_prefix(k, "color.push.", &slot_name)) { + int slot = parse_push_color_slot(slot_name); + if (slot < 0) + return 0; + if (!v) + return config_error_nonbool(k); + return color_parse(v, push_colors[slot]); + } else if (!strcmp(k, "push.useforceifincludes")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FORCE_IF_INCLUDES; + else + *flags &= ~TRANSPORT_PUSH_FORCE_IF_INCLUDES; + return 0; + } - return 0; + return git_default_config(k, v, NULL); } int cmd_push(int argc, const char **argv, const char *prefix) { int flags = 0; int tags = 0; + int push_cert = -1; int rc; const char *repo = NULL; /* default repository */ + struct string_list push_options_cmdline = STRING_LIST_INIT_DUP; + struct string_list *push_options; + const struct string_list_item *item; + struct remote *remote; + struct option options[] = { OPT__VERBOSITY(&verbosity), 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, N_("delete refs")), - OPT_BOOLEAN( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")), + OPT_BOOL('d', "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, "recurse-submodules", &flags, N_("check"), - N_("control recursive pushing of submodules"), - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, - OPT_BOOLEAN( 0 , "thin", &thin, N_("use thin pack")), + OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option), + OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags, + N_("require remote updates to be integrated locally"), + TRANSPORT_PUSH_FORCE_IF_INCLUDES), + OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)", + N_("control recursive pushing of submodules"), option_parse_recurse_submodules), + OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE), 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"), @@ -399,27 +566,90 @@ int cmd_push(int argc, const char **argv, const char *prefix) 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_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed), + OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), + OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")), + OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), + TRANSPORT_FAMILY_IPV4), + OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), + TRANSPORT_FAMILY_IPV6), 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); + push_options = (push_options_cmdline.nr + ? &push_options_cmdline + : &push_options_config); + set_push_cert_flags(&flags, push_cert); 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 (recurse_submodules == RECURSE_SUBMODULES_CHECK) + flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; + else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) + flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND; + else if (recurse_submodules == RECURSE_SUBMODULES_ONLY) + flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; + if (tags) - add_refspec("refs/tags/*"); + refspec_append(&rs, "refs/tags/*"); if (argc > 0) { repo = argv[0]; - set_refspecs(argv + 1, argc - 1); + set_refspecs(argv + 1, argc - 1, repo); } - rc = do_push(repo, flags); + remote = pushremote_get(repo); + if (!remote) { + if (repo) + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); + } + + if (remote->mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if (flags & TRANSPORT_PUSH_ALL) { + if (tags) + die(_("--all and --tags are incompatible")); + if (argc >= 2) + die(_("--all can't be combined with refspecs")); + } + if (flags & TRANSPORT_PUSH_MIRROR) { + if (tags) + die(_("--mirror and --tags are incompatible")); + if (argc >= 2) + die(_("--mirror can't be combined with refspecs")); + } + if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR)) + die(_("--all and --mirror are incompatible")); + + if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES)) + cas.use_force_if_includes = 1; + + for_each_string_list_item(item, push_options) + if (strchr(item->string, '\n')) + die(_("push options must not have new line characters")); + + rc = do_push(flags, push_options, remote); + string_list_clear(&push_options_cmdline, 0); + string_list_clear(&push_options_config, 0); if (rc == -1) usage_with_options(push_usage, options); else |