Skip to content

Commit 2233ad4

Browse files
committed
Merge branch 'jc/push-cas'
Allow a safer "rewind of the remote tip" push than blind "--force", by requiring that the overwritten remote ref to be unchanged since the new history to replace it was prepared. The machinery is more or less ready. The "--force" option is again the big red button to override any safety, thanks to J6t's sanity (the original round allowed --lockref to defeat --force). The logic to choose the default implemented here is fragile (e.g. "git fetch" after seeing a failure will update the remote-tracking branch and will make the next "push" pass, defeating the safety pretty easily). It is suitable only for the simplest workflows, and it may hurt users more than it helps them. * jc/push-cas: push: teach --force-with-lease to smart-http transport send-pack: fix parsing of --force-with-lease option t5540/5541: smart-http does not support "--force-with-lease" t5533: test "push --force-with-lease" push --force-with-lease: tie it all together push --force-with-lease: implement logic to populate old_sha1_expect[] remote.c: add command line option parser for "--force-with-lease" builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN cache.h: move remote/connect API out of it
2 parents 711b276 + 05c1eb1 commit 2233ad4

22 files changed

+638
-98
lines changed

Documentation/git-push.txt

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SYNOPSIS
1111
[verse]
1212
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
1313
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
14+
[--force-with-lease[=<refname>[:<expect>]]]
1415
[--no-verify] [<repository> [<refspec>...]]
1516

1617
DESCRIPTION
@@ -130,21 +131,75 @@ already exists on the remote side.
130131
repository over ssh, and you do not have the program in
131132
a directory on the default $PATH.
132133

134+
--[no-]force-with-lease::
135+
--force-with-lease=<refname>::
136+
--force-with-lease=<refname>:<expect>::
137+
Usually, "git push" refuses to update a remote ref that is
138+
not an ancestor of the local ref used to overwrite it.
139+
+
140+
This option bypasses the check, but instead requires that the
141+
current value of the ref to be the expected value. "git push"
142+
fails otherwise.
143+
+
144+
Imagine that you have to rebase what you have already published.
145+
You will have to bypass the "must fast-forward" rule in order to
146+
replace the history you originally published with the rebased history.
147+
If somebody else built on top of your original history while you are
148+
rebasing, the tip of the branch at the remote may advance with her
149+
commit, and blindly pushing with `--force` will lose her work.
150+
+
151+
This option allows you to say that you expect the history you are
152+
updating is what you rebased and want to replace. If the remote ref
153+
still points at the commit you specified, you can be sure that no
154+
other people did anything to the ref (it is like taking a "lease" on
155+
the ref without explicitly locking it, and you update the ref while
156+
making sure that your earlier "lease" is still valid).
157+
+
158+
`--force-with-lease` alone, without specifying the details, will protect
159+
all remote refs that are going to be updated by requiring their
160+
current value to be the same as the remote-tracking branch we have
161+
for them, unless specified with a `--force-with-lease=<refname>:<expect>`
162+
option that explicitly states what the expected value is.
163+
+
164+
`--force-with-lease=<refname>`, without specifying the expected value, will
165+
protect the named ref (alone), if it is going to be updated, by
166+
requiring its current value to be the same as the remote-tracking
167+
branch we have for it.
168+
+
169+
`--force-with-lease=<refname>:<expect>` will protect the named ref (alone),
170+
if it is going to be updated, by requiring its current value to be
171+
the same as the specified value <expect> (which is allowed to be
172+
different from the remote-tracking branch we have for the refname,
173+
or we do not even have to have such a remote-tracking branch when
174+
this form is used).
175+
+
176+
Note that all forms other than `--force-with-lease=<refname>:<expect>`
177+
that specifies the expected current value of the ref explicitly are
178+
still experimental and their semantics may change as we gain experience
179+
with this feature.
180+
+
181+
"--no-force-with-lease" will cancel all the previous --force-with-lease on the
182+
command line.
183+
133184
-f::
134185
--force::
135186
Usually, the command refuses to update a remote ref that is
136187
not an ancestor of the local ref used to overwrite it.
137-
This flag disables the check. This can cause the
138-
remote repository to lose commits; use it with care.
139-
Note that `--force` applies to all the refs that are pushed,
140-
hence using it with `push.default` set to `matching` or with
141-
multiple push destinations configured with `remote.*.push`
142-
may overwrite refs other than the current branch (including
143-
local refs that are strictly behind their remote counterpart).
144-
To force a push to only one branch, use a `+` in front of the
145-
refspec to push (e.g `git push origin +master` to force a push
146-
to the `master` branch). See the `<refspec>...` section above
147-
for details.
188+
Also, when `--force-with-lease` option is used, the command refuses
189+
to update a remote ref whose current value does not match
190+
what is expected.
191+
+
192+
This flag disables these checks, and can cause the remote repository
193+
to lose commits; use it with care.
194+
+
195+
Note that `--force` applies to all the refs that are pushed, hence
196+
using it with `push.default` set to `matching` or with multiple push
197+
destinations configured with `remote.*.push` may overwrite refs
198+
other than the current branch (including local refs that are
199+
strictly behind their remote counterpart). To force a push to only
200+
one branch, use a `+` in front of the refspec to push (e.g `git push
201+
origin +master` to force a push to the `master` branch). See the
202+
`<refspec>...` section above for details.
148203

149204
--repo=<repository>::
150205
This option is only relevant if no <repository> argument is

builtin/fetch-pack.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "builtin.h"
22
#include "pkt-line.h"
33
#include "fetch-pack.h"
4+
#include "remote.h"
5+
#include "connect.h"
46

57
static const char fetch_pack_usage[] =
68
"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "

builtin/push.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ static const char *receivepack;
2121
static int verbosity;
2222
static int progress = -1;
2323

24+
static struct push_cas_option cas;
25+
2426
static const char **refspec;
2527
static int refspec_nr;
2628
static int refspec_alloc;
@@ -316,6 +318,13 @@ static int push_with_options(struct transport *transport, int flags)
316318
if (thin)
317319
transport_set_option(transport, TRANS_OPT_THIN, "yes");
318320

321+
if (!is_empty_cas(&cas)) {
322+
if (!transport->smart_options)
323+
die("underlying transport does not support --%s option",
324+
CAS_OPT_NAME);
325+
transport->smart_options->cas = &cas;
326+
}
327+
319328
if (verbosity > 0)
320329
fprintf(stderr, _("Pushing to %s\n"), transport->url);
321330
err = transport_push(transport, refspec_nr, refspec, flags,
@@ -451,6 +460,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
451460
OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
452461
OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
453462
OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
463+
{ OPTION_CALLBACK,
464+
0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
465+
N_("require old value of ref to be at this value"),
466+
PARSE_OPT_OPTARG, parseopt_push_cas_option },
454467
{ OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"),
455468
N_("control recursive pushing of submodules"),
456469
PARSE_OPT_OPTARG, option_parse_recurse_submodules },

builtin/receive-pack.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "commit.h"
99
#include "object.h"
1010
#include "remote.h"
11+
#include "connect.h"
1112
#include "transport.h"
1213
#include "string-list.h"
1314
#include "sha1-array.h"

builtin/send-pack.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "sideband.h"
66
#include "run-command.h"
77
#include "remote.h"
8+
#include "connect.h"
89
#include "send-pack.h"
910
#include "quote.h"
1011
#include "transport.h"
@@ -54,6 +55,11 @@ static void print_helper_status(struct ref *ref)
5455
msg = "needs force";
5556
break;
5657

58+
case REF_STATUS_REJECT_STALE:
59+
res = "error";
60+
msg = "stale info";
61+
break;
62+
5763
case REF_STATUS_REJECT_ALREADY_EXISTS:
5864
res = "error";
5965
msg = "already exists";
@@ -102,6 +108,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
102108
int flags;
103109
unsigned int reject_reasons;
104110
int progress = -1;
111+
struct push_cas_option cas = {0};
105112

106113
argv++;
107114
for (i = 1; i < argc; i++, argv++) {
@@ -164,6 +171,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
164171
helper_status = 1;
165172
continue;
166173
}
174+
if (!strcmp(arg, "--" CAS_OPT_NAME)) {
175+
if (parse_push_cas_option(&cas, NULL, 0) < 0)
176+
exit(1);
177+
continue;
178+
}
179+
if (!strcmp(arg, "--no-" CAS_OPT_NAME)) {
180+
if (parse_push_cas_option(&cas, NULL, 1) < 0)
181+
exit(1);
182+
continue;
183+
}
184+
if (!prefixcmp(arg, "--" CAS_OPT_NAME "=")) {
185+
if (parse_push_cas_option(&cas,
186+
strchr(arg, '=') + 1, 0) < 0)
187+
exit(1);
188+
continue;
189+
}
167190
usage(send_pack_usage);
168191
}
169192
if (!dest) {
@@ -224,6 +247,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
224247
if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
225248
return -1;
226249

250+
if (!is_empty_cas(&cas))
251+
apply_push_cas(&cas, remote, remote_refs);
252+
227253
set_ref_status_for_push(remote_refs, args.send_mirror,
228254
args.force_update);
229255

cache.h

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,68 +1038,6 @@ struct pack_entry {
10381038
struct packed_git *p;
10391039
};
10401040

1041-
struct ref {
1042-
struct ref *next;
1043-
unsigned char old_sha1[20];
1044-
unsigned char new_sha1[20];
1045-
char *symref;
1046-
unsigned int
1047-
force:1,
1048-
forced_update:1,
1049-
deletion:1,
1050-
matched:1;
1051-
1052-
/*
1053-
* Order is important here, as we write to FETCH_HEAD
1054-
* in numeric order. And the default NOT_FOR_MERGE
1055-
* should be 0, so that xcalloc'd structures get it
1056-
* by default.
1057-
*/
1058-
enum {
1059-
FETCH_HEAD_MERGE = -1,
1060-
FETCH_HEAD_NOT_FOR_MERGE = 0,
1061-
FETCH_HEAD_IGNORE = 1
1062-
} fetch_head_status;
1063-
1064-
enum {
1065-
REF_STATUS_NONE = 0,
1066-
REF_STATUS_OK,
1067-
REF_STATUS_REJECT_NONFASTFORWARD,
1068-
REF_STATUS_REJECT_ALREADY_EXISTS,
1069-
REF_STATUS_REJECT_NODELETE,
1070-
REF_STATUS_REJECT_FETCH_FIRST,
1071-
REF_STATUS_REJECT_NEEDS_FORCE,
1072-
REF_STATUS_UPTODATE,
1073-
REF_STATUS_REMOTE_REJECT,
1074-
REF_STATUS_EXPECTING_REPORT
1075-
} status;
1076-
char *remote_status;
1077-
struct ref *peer_ref; /* when renaming */
1078-
char name[FLEX_ARRAY]; /* more */
1079-
};
1080-
1081-
#define REF_NORMAL (1u << 0)
1082-
#define REF_HEADS (1u << 1)
1083-
#define REF_TAGS (1u << 2)
1084-
1085-
extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
1086-
1087-
#define CONNECT_VERBOSE (1u << 0)
1088-
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
1089-
extern int finish_connect(struct child_process *conn);
1090-
extern int git_connection_is_socket(struct child_process *conn);
1091-
struct extra_have_objects {
1092-
int nr, alloc;
1093-
unsigned char (*array)[20];
1094-
};
1095-
extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
1096-
struct ref **list, unsigned int flags,
1097-
struct extra_have_objects *);
1098-
extern int server_supports(const char *feature);
1099-
extern int parse_feature_request(const char *features, const char *feature);
1100-
extern const char *server_feature_value(const char *feature, int *len_ret);
1101-
extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
1102-
11031041
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
11041042

11051043
/* A hook for count-objects to report invalid files in pack directory */

connect.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "refs.h"
66
#include "run-command.h"
77
#include "remote.h"
8+
#include "connect.h"
89
#include "url.h"
910

1011
static char *server_capabilities;

connect.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef CONNECT_H
2+
#define CONNECT_H
3+
4+
#define CONNECT_VERBOSE (1u << 0)
5+
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
6+
extern int finish_connect(struct child_process *conn);
7+
extern int git_connection_is_socket(struct child_process *conn);
8+
extern int server_supports(const char *feature);
9+
extern int parse_feature_request(const char *features, const char *feature);
10+
extern const char *server_feature_value(const char *feature, int *len_ret);
11+
extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
12+
13+
#endif

fetch-pack.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "fetch-pack.h"
1010
#include "remote.h"
1111
#include "run-command.h"
12+
#include "connect.h"
1213
#include "transport.h"
1314
#include "version.h"
1415
#include "prio-queue.h"

fetch-pack.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define FETCH_PACK_H
33

44
#include "string-list.h"
5+
#include "run-command.h"
56

67
struct fetch_pack_args {
78
const char *uploadpack;

0 commit comments

Comments
 (0)