Skip to content

Commit 5a77bc8

Browse files
committed
Merge branch 'sk/force-if-includes' into seen
"git push --force-with-lease[=<ref>]" can easily be misused to lose commits unless the user takes good care of their own "git fetch". A new option "--force-if-includes" attempts to ensure that what is being force-pushed was created after examining the commit at the tip of the remote ref that is about to be force-replaced. * sk/force-if-includes: SQUASH??? name an array in singular and avoid commenting obvious t, doc: update tests, reference for "--force-if-includes" push: parse and set flag for "--force-if-includes" push: add reflog check for "--force-if-includes"
2 parents e17a4f5 + 696a73e commit 5a77bc8

File tree

15 files changed

+449
-18
lines changed

15 files changed

+449
-18
lines changed

Documentation/config/advice.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ advice.*::
1010
that the check is disabled.
1111
pushUpdateRejected::
1212
Set this variable to 'false' if you want to disable
13-
'pushNonFFCurrent',
14-
'pushNonFFMatching', 'pushAlreadyExists',
15-
'pushFetchFirst', and 'pushNeedsForce'
13+
'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
14+
'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
1615
simultaneously.
1716
pushNonFFCurrent::
1817
Advice shown when linkgit:git-push[1] fails due to a
@@ -41,6 +40,10 @@ advice.*::
4140
we can still suggest that the user push to either
4241
refs/heads/* or refs/tags/* based on the type of the
4342
source object.
43+
pushRefNeedsUpdate::
44+
Shown when linkgit:git-push[1] rejects a forced update of
45+
a branch when its remote-tracking ref has updates that we
46+
do not have locally.
4447
statusAheadBehind::
4548
Shown when linkgit:git-status[1] computes the ahead/behind
4649
counts for a local ref compared to its remote tracking ref,

Documentation/config/push.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,9 @@ push.recurseSubmodules::
114114
specifying '--recurse-submodules=check|on-demand|no'.
115115
If not set, 'no' is used by default, unless 'submodule.recurse' is
116116
set (in which case a 'true' value means 'on-demand').
117+
118+
push.useForceIfIncludes::
119+
If set to "true", it is equivalent to specifying
120+
`--force-if-includes` as an option to linkgit:git-push[1]
121+
in the command line. Adding `--no-force-if-includes` at the
122+
time of push overrides this configuration setting.

Documentation/git-push.txt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SYNOPSIS
1313
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
1414
[-u | --set-upstream] [-o <string> | --push-option=<string>]
1515
[--[no-]signed|--signed=(true|false|if-asked)]
16-
[--force-with-lease[=<refname>[:<expect>]]]
16+
[--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
1717
[--no-verify] [<repository> [<refspec>...]]
1818

1919
DESCRIPTION
@@ -320,6 +320,14 @@ seen and are willing to overwrite, then rewrite history, and finally
320320
force push changes to `master` if the remote version is still at
321321
`base`, regardless of what your local `remotes/origin/master` has been
322322
updated to in the background.
323+
+
324+
Alternatively, specifying `--force-if-includes` an an ancillary option
325+
along with `--force-with-lease[=<refname>]` (i.e., without saying what
326+
exact commit the ref on the remote side must be pointing at, or which
327+
refs on the remote side are being protected) at the time of "push" will
328+
verify if updates from the remote-tracking refs that may have been
329+
implicitly updated in the background are integrated locally before
330+
allowing a forced update.
323331

324332
-f::
325333
--force::
@@ -341,6 +349,22 @@ one branch, use a `+` in front of the refspec to push (e.g `git push
341349
origin +master` to force a push to the `master` branch). See the
342350
`<refspec>...` section above for details.
343351

352+
--[no-]force-if-includes::
353+
Force an update only if the tip of the remote-tracking ref
354+
has been integrated locally.
355+
+
356+
This option enables a check that verifies if the tip of the
357+
remote-tracking ref is reachable from one of the "reflog" entries of
358+
the local branch based in it for a rewrite. The check ensures that any
359+
updates from the remote have been incorporated locally by rejecting the
360+
forced update if that is not the case.
361+
+
362+
If the option is passed without specifying `--force-with-lease`, or
363+
specified along with `--force-with-lease=<refname>:<expect>`, it is
364+
a "no-op".
365+
+
366+
Specifying `--no-force-if-includes` disables this behavior.
367+
344368
--repo=<repository>::
345369
This option is equivalent to the <repository> argument. If both
346370
are specified, the command-line argument takes precedence.

advice.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ int advice_push_already_exists = 1;
1111
int advice_push_fetch_first = 1;
1212
int advice_push_needs_force = 1;
1313
int advice_push_unqualified_ref_name = 1;
14+
int advice_push_ref_needs_update = 1;
1415
int advice_status_hints = 1;
1516
int advice_status_u_option = 1;
1617
int advice_status_ahead_behind_warning = 1;
@@ -72,6 +73,7 @@ static struct {
7273
{ "pushFetchFirst", &advice_push_fetch_first },
7374
{ "pushNeedsForce", &advice_push_needs_force },
7475
{ "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
76+
{ "pushRefNeedsUpdate", &advice_push_ref_needs_update },
7577
{ "statusHints", &advice_status_hints },
7678
{ "statusUoption", &advice_status_u_option },
7779
{ "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
@@ -116,6 +118,7 @@ static struct {
116118
[ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists", 1 },
117119
[ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst", 1 },
118120
[ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce", 1 },
121+
[ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate", 1 },
119122

120123
/* make this an alias for backward compatibility */
121124
[ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 },

advice.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern int advice_push_already_exists;
1111
extern int advice_push_fetch_first;
1212
extern int advice_push_needs_force;
1313
extern int advice_push_unqualified_ref_name;
14+
extern int advice_push_ref_needs_update;
1415
extern int advice_status_hints;
1516
extern int advice_status_u_option;
1617
extern int advice_status_ahead_behind_warning;
@@ -60,6 +61,7 @@ extern int advice_add_empty_pathspec;
6061
ADVICE_PUSH_UNQUALIFIED_REF_NAME,
6162
ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
6263
ADVICE_PUSH_UPDATE_REJECTED,
64+
ADVICE_PUSH_REF_NEEDS_UPDATE,
6365
ADVICE_RESET_QUIET_WARNING,
6466
ADVICE_RESOLVE_CONFLICT,
6567
ADVICE_RM_HINTS,

builtin/push.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@ static const char message_advice_ref_needs_force[] =
290290
"or update a remote ref to make it point at a non-commit object,\n"
291291
"without using the '--force' option.\n");
292292

293+
static const char message_advice_ref_needs_update[] =
294+
N_("Updates were rejected because the tip of the remote-tracking\n"
295+
"branch has been updated since the last checkout. You may want\n"
296+
"to integrate those changes locally (e.g., 'git pull ...')\n"
297+
"before forcing an update.\n");
298+
293299
static void advise_pull_before_push(void)
294300
{
295301
if (!advice_push_non_ff_current || !advice_push_update_rejected)
@@ -325,6 +331,13 @@ static void advise_ref_needs_force(void)
325331
advise(_(message_advice_ref_needs_force));
326332
}
327333

334+
static void advise_ref_needs_update(void)
335+
{
336+
if (!advice_push_ref_needs_update || !advice_push_update_rejected)
337+
return;
338+
advise(_(message_advice_ref_needs_update));
339+
}
340+
328341
static int push_with_options(struct transport *transport, struct refspec *rs,
329342
int flags)
330343
{
@@ -374,6 +387,8 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
374387
advise_ref_fetch_first();
375388
} else if (reject_reasons & REJECT_NEEDS_FORCE) {
376389
advise_ref_needs_force();
390+
} else if (reject_reasons & REJECT_REF_NEEDS_UPDATE) {
391+
advise_ref_needs_update();
377392
}
378393

379394
return 1;
@@ -510,6 +525,12 @@ static int git_push_config(const char *k, const char *v, void *cb)
510525
if (!v)
511526
return config_error_nonbool(k);
512527
return color_parse(v, push_colors[slot]);
528+
} else if (!strcmp(k, "push.useforceifincludes")) {
529+
if (git_config_bool(k, v))
530+
*flags |= TRANSPORT_PUSH_FORCE_IF_INCLUDES;
531+
else
532+
*flags &= ~TRANSPORT_PUSH_FORCE_IF_INCLUDES;
533+
return 0;
513534
}
514535

515536
return git_default_config(k, v, NULL);
@@ -541,6 +562,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
541562
OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
542563
N_("require old value of ref to be at this value"),
543564
PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),
565+
OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,
566+
N_("require remote updates to be integrated locally"),
567+
TRANSPORT_PUSH_FORCE_IF_INCLUDES),
544568
OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
545569
N_("control recursive pushing of submodules"), option_parse_recurse_submodules),
546570
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)
625649
if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR))
626650
die(_("--all and --mirror are incompatible"));
627651

652+
if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES))
653+
cas.use_force_if_includes = 1;
654+
628655
for_each_string_list_item(item, push_options)
629656
if (strchr(item->string, '\n'))
630657
die(_("push options must not have new line characters"));

builtin/send-pack.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ static void print_helper_status(struct ref *ref)
7171
msg = "stale info";
7272
break;
7373

74+
case REF_STATUS_REJECT_REMOTE_UPDATED:
75+
res = "error";
76+
msg = "remote ref updated since checkout";
77+
break;
78+
7479
case REF_STATUS_REJECT_ALREADY_EXISTS:
7580
res = "error";
7681
msg = "already exists";
@@ -173,6 +178,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
173178
int progress = -1;
174179
int from_stdin = 0;
175180
struct push_cas_option cas = {0};
181+
int force_if_includes = 0;
176182
struct packet_reader reader;
177183

178184
struct option options[] = {
@@ -198,6 +204,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
198204
OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
199205
N_("require old value of ref to be at this value"),
200206
PARSE_OPT_OPTARG, parseopt_push_cas_option),
207+
OPT_BOOL(0, TRANS_OPT_FORCE_IF_INCLUDES, &force_if_includes,
208+
N_("require remote updates to be integrated locally")),
201209
OPT_END()
202210
};
203211

@@ -299,6 +307,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
299307
if (!is_empty_cas(&cas))
300308
apply_push_cas(&cas, remote, remote_refs);
301309

310+
if (!is_empty_cas(&cas) && force_if_includes)
311+
cas.use_force_if_includes = 1;
312+
313+
302314
set_ref_status_for_push(remote_refs, args.send_mirror,
303315
args.force_update);
304316

remote-curl.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ struct options {
4444
from_promisor : 1,
4545

4646
atomic : 1,
47-
object_format : 1;
47+
object_format : 1,
48+
force_if_includes : 1;
4849
const struct git_hash_algo *hash_algo;
4950
};
5051
static struct options options;
@@ -131,6 +132,14 @@ static int set_option(const char *name, const char *value)
131132
string_list_append(&cas_options, val.buf);
132133
strbuf_release(&val);
133134
return 0;
135+
} else if (!strcmp(name, TRANS_OPT_FORCE_IF_INCLUDES)) {
136+
if (!strcmp(value, "true"))
137+
options.force_if_includes = 1;
138+
else if (!strcmp(value, "false"))
139+
options.force_if_includes = 0;
140+
else
141+
return -1;
142+
return 0;
134143
} else if (!strcmp(name, "cloning")) {
135144
if (!strcmp(value, "true"))
136145
options.cloning = 1;
@@ -1318,6 +1327,9 @@ static int push_git(struct discovery *heads, int nr_spec, const char **specs)
13181327
strvec_push(&args, cas_option->string);
13191328
strvec_push(&args, url.buf);
13201329

1330+
if (options.force_if_includes)
1331+
strvec_push(&args, "--force-if-includes");
1332+
13211333
strvec_push(&args, "--stdin");
13221334
for (i = 0; i < nr_spec; i++)
13231335
packet_buf_write(&preamble, "%s\n", specs[i]);

0 commit comments

Comments
 (0)