The previous commit added the necessary machinery to implement the "--force-if-includes" protection, when "--force-with-lease" is used without giving exact object the remote still ought to have. Surface the feature by adding a command line option and a configuration variable to enable it. - Add a flag: "TRANSPORT_PUSH_FORCE_IF_INCLUDES" to indicate that the new option was passed from the command line of via configuration settings; update command line and configuration parsers to set the new flag accordingly. - Introduce a new configuration option "push.useForceIfIncludes", which is equivalent to setting "--force-if-includes" in the command line. - Update "remote-curl" to recognize and pass this option to "send-pack" when enabled. - Update "advise" to catch the reject reason "REJECT_REF_NEEDS_UPDATE", set when the ref status is "REF_STATUS_REJECT_REMOTE_UPDATED" and (optionally) print a help message when the push fails. - The new option is a "no-op" in the following scenarios: * When used without "--force-with-lease". * When used with "--force-with-lease", and if the expected commit on the remote side is specified as an argument. Signed-off-by: Srinidhi Kaushik <shrinidhi.kaushik@xxxxxxxxx> --- advice.c | 3 +++ advice.h | 2 ++ builtin/push.c | 27 +++++++++++++++++++++++++++ builtin/send-pack.c | 6 ++++++ remote-curl.c | 14 +++++++++++++- transport-helper.c | 5 +++++ transport.c | 2 ++ transport.h | 15 ++++++++++----- 8 files changed, 68 insertions(+), 6 deletions(-) diff --git a/advice.c b/advice.c index f0a3d32d20..164742305f 100644 --- a/advice.c +++ b/advice.c @@ -11,6 +11,7 @@ int advice_push_already_exists = 1; int advice_push_fetch_first = 1; int advice_push_needs_force = 1; int advice_push_unqualified_ref_name = 1; +int advice_push_ref_needs_update = 1; int advice_status_hints = 1; int advice_status_u_option = 1; int advice_status_ahead_behind_warning = 1; @@ -72,6 +73,7 @@ static struct { { "pushFetchFirst", &advice_push_fetch_first }, { "pushNeedsForce", &advice_push_needs_force }, { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name }, + { "pushRefNeedsUpdate", &advice_push_ref_needs_update }, { "statusHints", &advice_status_hints }, { "statusUoption", &advice_status_u_option }, { "statusAheadBehindWarning", &advice_status_ahead_behind_warning }, @@ -116,6 +118,7 @@ static struct { [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists", 1 }, [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst", 1 }, [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce", 1 }, + [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate", 1 }, /* make this an alias for backward compatibility */ [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 }, diff --git a/advice.h b/advice.h index 16f2c11642..bc2432980a 100644 --- a/advice.h +++ b/advice.h @@ -11,6 +11,7 @@ extern int advice_push_already_exists; extern int advice_push_fetch_first; extern int advice_push_needs_force; extern int advice_push_unqualified_ref_name; +extern int advice_push_ref_needs_update; extern int advice_status_hints; extern int advice_status_u_option; extern int advice_status_ahead_behind_warning; @@ -60,6 +61,7 @@ extern int advice_add_empty_pathspec; ADVICE_PUSH_UNQUALIFIED_REF_NAME, ADVICE_PUSH_UPDATE_REJECTED_ALIAS, ADVICE_PUSH_UPDATE_REJECTED, + ADVICE_PUSH_REF_NEEDS_UPDATE, ADVICE_RESET_QUIET_WARNING, ADVICE_RESOLVE_CONFLICT, ADVICE_RM_HINTS, diff --git a/builtin/push.c b/builtin/push.c index 0eeb2c8dd5..908b557edb 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -290,6 +290,12 @@ static const char message_advice_ref_needs_force[] = "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_update_rejected) @@ -325,6 +331,13 @@ static void advise_ref_needs_force(void) advise(_(message_advice_ref_needs_force)); } +static void advise_ref_needs_update(void) +{ + if (!advice_push_ref_needs_update || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_needs_update)); +} + static int push_with_options(struct transport *transport, struct refspec *rs, int flags) { @@ -374,6 +387,8 @@ static int push_with_options(struct transport *transport, struct refspec *rs, 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; @@ -510,6 +525,12 @@ static int git_push_config(const char *k, const char *v, void *cb) 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 git_default_config(k, v, NULL); @@ -541,6 +562,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) 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), @@ -625,6 +649,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) 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")); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 516cba7336..a7e01667b0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -178,6 +178,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int progress = -1; int from_stdin = 0; struct push_cas_option cas = {0}; + int force_if_includes = 0; struct packet_reader reader; struct option options[] = { @@ -203,6 +204,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) 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, parseopt_push_cas_option), + OPT_BOOL(0, TRANS_OPT_FORCE_IF_INCLUDES, &force_if_includes, + N_("require remote updates to be integrated locally")), OPT_END() }; @@ -304,6 +307,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (!is_empty_cas(&cas)) apply_push_cas(&cas, remote, remote_refs); + if (!is_empty_cas(&cas) && force_if_includes) + cas.use_force_if_includes = 1; + set_ref_status_for_push(remote_refs, args.send_mirror, args.force_update); diff --git a/remote-curl.c b/remote-curl.c index 32cc4a0c55..0290b04891 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -44,7 +44,8 @@ struct options { from_promisor : 1, atomic : 1, - object_format : 1; + object_format : 1, + force_if_includes : 1; const struct git_hash_algo *hash_algo; }; static struct options options; @@ -131,6 +132,14 @@ static int set_option(const char *name, const char *value) string_list_append(&cas_options, val.buf); strbuf_release(&val); return 0; + } else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) { + if (!strcmp(value, "true")) + options.force_if_includes = 1; + else if (!strcmp(value, "false")) + options.force_if_includes = 0; + else + return -1; + return 0; } else if (!strcmp(name, "cloning")) { if (!strcmp(value, "true")) options.cloning = 1; @@ -1318,6 +1327,9 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs) strvec_push(&args, cas_option->string); strvec_push(&args, url.buf); + if (options.force_if_includes) + strvec_push(&args, "--force-if-includes"); + strvec_push(&args, "--stdin"); for (i = 0; i < nr_spec; i++) packet_buf_write(&preamble, "%s\n", specs[i]); diff --git a/transport-helper.c b/transport-helper.c index 6157de30c7..5f6e0b3bd8 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -938,6 +938,11 @@ static void set_common_push_options(struct transport *transport, if (set_helper_option(transport, TRANS_OPT_ATOMIC, "true") != 0) die(_("helper %s does not support --atomic"), name); + if (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES) + if (set_helper_option(transport, TRANS_OPT_FORCE_IF_INCLUDES, "true") != 0) + die(_("helper %s does not support --%s"), + name, TRANS_OPT_FORCE_IF_INCLUDES); + if (flags & TRANSPORT_PUSH_OPTIONS) { struct string_list_item *item; for_each_string_list_item(item, transport->push_options) diff --git a/transport.c b/transport.c index 65fcd22b20..47da955e4f 100644 --- a/transport.c +++ b/transport.c @@ -748,6 +748,8 @@ void transport_print_push_status(const char *dest, struct ref *refs, *reject_reasons |= REJECT_FETCH_FIRST; } else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) { *reject_reasons |= REJECT_NEEDS_FORCE; + } else if (ref->status == REF_STATUS_REJECT_REMOTE_UPDATED) { + *reject_reasons |= REJECT_REF_NEEDS_UPDATE; } } free(head); diff --git a/transport.h b/transport.h index ca409ea1e4..24558c027d 100644 --- a/transport.h +++ b/transport.h @@ -136,6 +136,7 @@ struct transport { #define TRANSPORT_PUSH_ATOMIC (1<<13) #define TRANSPORT_PUSH_OPTIONS (1<<14) #define TRANSPORT_RECURSE_SUBMODULES_ONLY (1<<15) +#define TRANSPORT_PUSH_FORCE_IF_INCLUDES (1<<16) int transport_summary_width(const struct ref *refs); @@ -208,6 +209,9 @@ void transport_check_allowed(const char *type); /* Request atomic (all-or-nothing) updates when pushing */ #define TRANS_OPT_ATOMIC "atomic" +/* Require remote changes to be integrated locally. */ +#define TRANS_OPT_FORCE_IF_INCLUDES "force-if-includes" + /** * Returns 0 if the option was used, non-zero otherwise. Prints a * message to stderr if the option is not used. @@ -217,11 +221,12 @@ int transport_set_option(struct transport *transport, const char *name, void transport_set_verbosity(struct transport *transport, int verbosity, int force_progress); -#define REJECT_NON_FF_HEAD 0x01 -#define REJECT_NON_FF_OTHER 0x02 -#define REJECT_ALREADY_EXISTS 0x04 -#define REJECT_FETCH_FIRST 0x08 -#define REJECT_NEEDS_FORCE 0x10 +#define REJECT_NON_FF_HEAD 0x01 +#define REJECT_NON_FF_OTHER 0x02 +#define REJECT_ALREADY_EXISTS 0x04 +#define REJECT_FETCH_FIRST 0x08 +#define REJECT_NEEDS_FORCE 0x10 +#define REJECT_REF_NEEDS_UPDATE 0x20 int transport_push(struct repository *repo, struct transport *connection, -- 2.28.0