Skip to content

Commit

Permalink
push --force-with-lease: tie it all together
Browse files Browse the repository at this point in the history
This teaches the deepest part of the callchain for "git push" (and
"git send-pack") to enforce "the old value of the ref must be this,
otherwise fail this push" (aka "compare-and-swap" / "--lockref").

Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Junio C Hamano committed Jul 23, 2013
1 parent 91048a9 commit 631b5ef
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 13 deletions.
5 changes: 5 additions & 0 deletions builtin/send-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ static void print_helper_status(struct ref *ref)
msg = "needs force";
break;

case REF_STATUS_REJECT_STALE:
res = "error";
msg = "stale info";
break;

case REF_STATUS_REJECT_ALREADY_EXISTS:
res = "error";
msg = "already exists";
Expand Down
49 changes: 36 additions & 13 deletions remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -1396,12 +1396,13 @@ int match_push_refs(struct ref *src, struct ref **dst,
}

void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
int force_update)
int force_update)
{
struct ref *ref;

for (ref = remote_refs; ref; ref = ref->next) {
int force_ref_update = ref->force || force_update;
int reject_reason = 0;

if (ref->peer_ref)
hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
Expand All @@ -1416,6 +1417,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
}

/*
* Bypass the usual "must fast-forward" check but
* replace it with a weaker "the old value must be
* this value we observed". If the remote ref has
* moved and is now different from what we expect,
* reject any push.
*
* It also is an error if the user told us to check
* with the remote-tracking branch to find the value
* to expect, but we did not have such a tracking
* branch.
*/
if (ref->expect_old_sha1) {
if (ref->expect_old_no_trackback ||
hashcmp(ref->old_sha1, ref->old_sha1_expect))
reject_reason = REF_STATUS_REJECT_STALE;
}

/*
* The usual "must fast-forward" rules.
*
* Decide whether an individual refspec A:B can be
* pushed. The push will succeed if any of the
* following are true:
Expand All @@ -1433,24 +1454,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
* passing the --force argument
*/

if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
int why = 0; /* why would this push require --force? */

else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
if (!prefixcmp(ref->name, "refs/tags/"))
why = REF_STATUS_REJECT_ALREADY_EXISTS;
reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
else if (!has_sha1_file(ref->old_sha1))
why = REF_STATUS_REJECT_FETCH_FIRST;
reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
!lookup_commit_reference_gently(ref->new_sha1, 1))
why = REF_STATUS_REJECT_NEEDS_FORCE;
reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
else if (!ref_newer(ref->new_sha1, ref->old_sha1))
why = REF_STATUS_REJECT_NONFASTFORWARD;

if (!force_ref_update)
ref->status = why;
else if (why)
ref->forced_update = 1;
reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
}

/*
* "--force" will defeat any rejection implemented
* by the rules above.
*/
if (!force_ref_update)
ref->status = reject_reason;
else if (reject_reason)
ref->forced_update = 1;
}
}

Expand Down
1 change: 1 addition & 0 deletions remote.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ struct ref {
REF_STATUS_REJECT_NODELETE,
REF_STATUS_REJECT_FETCH_FIRST,
REF_STATUS_REJECT_NEEDS_FORCE,
REF_STATUS_REJECT_STALE,
REF_STATUS_UPTODATE,
REF_STATUS_REMOTE_REJECT,
REF_STATUS_EXPECTING_REPORT
Expand Down
1 change: 1 addition & 0 deletions send-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ int send_pack(struct send_pack_args *args,
case REF_STATUS_REJECT_ALREADY_EXISTS:
case REF_STATUS_REJECT_FETCH_FIRST:
case REF_STATUS_REJECT_NEEDS_FORCE:
case REF_STATUS_REJECT_STALE:
case REF_STATUS_UPTODATE:
continue;
default:
Expand Down
6 changes: 6 additions & 0 deletions transport-helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ static int push_update_ref_status(struct strbuf *buf,
free(msg);
msg = NULL;
}
else if (!strcmp(msg, "stale info")) {
status = REF_STATUS_REJECT_STALE;
free(msg);
msg = NULL;
}
}

if (*ref)
Expand Down Expand Up @@ -756,6 +761,7 @@ static int push_refs_with_push(struct transport *transport,
/* Check for statuses set by set_ref_status_for_push() */
switch (ref->status) {
case REF_STATUS_REJECT_NONFASTFORWARD:
case REF_STATUS_REJECT_STALE:
case REF_STATUS_REJECT_ALREADY_EXISTS:
case REF_STATUS_UPTODATE:
continue;
Expand Down
5 changes: 5 additions & 0 deletions transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
"needs force", porcelain);
break;
case REF_STATUS_REJECT_STALE:
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
"stale info", porcelain);
break;
case REF_STATUS_REMOTE_REJECT:
print_ref_status('!', "[remote rejected]", ref,
ref->deletion ? NULL : ref->peer_ref,
Expand Down Expand Up @@ -1078,6 +1082,7 @@ static int run_pre_push_hook(struct transport *transport,
for (r = remote_refs; r; r = r->next) {
if (!r->peer_ref) continue;
if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
if (r->status == REF_STATUS_REJECT_STALE) continue;
if (r->status == REF_STATUS_UPTODATE) continue;

strbuf_reset(&buf);
Expand Down

0 comments on commit 631b5ef

Please sign in to comment.