Merge branch 'tc/push-force-if-includes-fixes' into seen
The --force-if-includes protection for git push has been updated to consult the reflog of the local branch being pushed, rather than incorrectly checking the reflog of a local branch that shares the name of the remote destination branch. The push advice for detached HEAD scenarios has also been adjusted to indicate that the remote ref cannot be verified locally. * tc/push-force-if-includes-fixes: push: --force-if-includes should allow fast-forward push: fix --force-if-includes non-branch advice push: check pushed ref for --force-if-includes
commit
8e7cd11a48
|
|
@ -98,6 +98,10 @@ all advice messages.
|
|||
Shown when linkgit:git-push[1] rejects a forced update of
|
||||
a branch when its remote-tracking ref has updates that we
|
||||
do not have locally.
|
||||
pushRefUnverifiable::
|
||||
Shown when linkgit:git-push[1] rejects a forced update of
|
||||
a branch when we are unable to verify the remote-tracking
|
||||
ref is integrated locally.
|
||||
pushRepoLooksLikeRef::
|
||||
Shown when the repository given to linkgit:git-push[1] is not
|
||||
a configured remote but looks like a `<remote>/<branch>` ref,
|
||||
|
|
|
|||
1
advice.c
1
advice.c
|
|
@ -70,6 +70,7 @@ static struct {
|
|||
[ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent" },
|
||||
[ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" },
|
||||
[ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" },
|
||||
[ADVICE_PUSH_REF_UNVERIFIABLE] = { "pushRefUnverifiable" },
|
||||
[ADVICE_PUSH_REPO_LOOKS_LIKE_REF] = { "pushRepoLooksLikeRef" },
|
||||
[ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" },
|
||||
[ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" },
|
||||
|
|
|
|||
1
advice.h
1
advice.h
|
|
@ -37,6 +37,7 @@ enum advice_type {
|
|||
ADVICE_PUSH_NON_FF_CURRENT,
|
||||
ADVICE_PUSH_NON_FF_MATCHING,
|
||||
ADVICE_PUSH_REF_NEEDS_UPDATE,
|
||||
ADVICE_PUSH_REF_UNVERIFIABLE,
|
||||
ADVICE_PUSH_REPO_LOOKS_LIKE_REF,
|
||||
ADVICE_PUSH_UNQUALIFIED_REF_NAME,
|
||||
ADVICE_PUSH_UPDATE_REJECTED,
|
||||
|
|
|
|||
|
|
@ -322,6 +322,13 @@ static const char message_advice_ref_needs_update[] =
|
|||
"remote changes, use 'git pull' before pushing again.\n"
|
||||
"See the 'Note about fast-forwards' in 'git push --help' for details.");
|
||||
|
||||
static const char message_advice_ref_unverifiable[] =
|
||||
N_("Updates were rejected because what you are pushing is not a branch,\n"
|
||||
"so there is no reflog to check against the tip of the remote-tracking\n"
|
||||
"branch. If you want to push anyway, specify the expected value with\n"
|
||||
"'--force-with-lease=<ref>:<expect>' or use '--no-force-if-includes'\n"
|
||||
"to skip this check.");
|
||||
|
||||
static void advise_pull_before_push(void)
|
||||
{
|
||||
if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
|
||||
|
|
@ -364,6 +371,14 @@ static void advise_ref_needs_update(void)
|
|||
advise(_(message_advice_ref_needs_update));
|
||||
}
|
||||
|
||||
static void advise_ref_unverifiable(void)
|
||||
{
|
||||
if (!advice_enabled(ADVICE_PUSH_REF_UNVERIFIABLE) ||
|
||||
!advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
|
||||
return;
|
||||
advise(_(message_advice_ref_unverifiable));
|
||||
}
|
||||
|
||||
static int push_with_options(struct transport *transport, struct refspec *rs,
|
||||
int flags)
|
||||
{
|
||||
|
|
@ -415,6 +430,8 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
|
|||
advise_ref_needs_force();
|
||||
} else if (reject_reasons & REJECT_REF_NEEDS_UPDATE) {
|
||||
advise_ref_needs_update();
|
||||
} else if (reject_reasons & REJECT_REF_UNVERIFIABLE) {
|
||||
advise_ref_unverifiable();
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ static void print_helper_status(struct ref *ref)
|
|||
msg = "remote ref updated since checkout";
|
||||
break;
|
||||
|
||||
case REF_STATUS_REJECT_UNVERIFIABLE:
|
||||
res = "error";
|
||||
msg = "remote ref unverifiable";
|
||||
break;
|
||||
|
||||
case REF_STATUS_REJECT_ALREADY_EXISTS:
|
||||
res = "error";
|
||||
msg = "already exists";
|
||||
|
|
|
|||
43
remote.c
43
remote.c
|
|
@ -1687,6 +1687,7 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
|||
for (ref = remote_refs; ref; ref = ref->next) {
|
||||
int force_ref_update = ref->force || force_update;
|
||||
int reject_reason = 0;
|
||||
int deferred_reject_reason = 0;
|
||||
|
||||
if (ref->peer_ref)
|
||||
oidcpy(&ref->new_oid, &ref->peer_ref->new_oid);
|
||||
|
|
@ -1711,14 +1712,18 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
|||
*
|
||||
* If the tip of the remote-tracking ref is unreachable
|
||||
* from any reflog entry of its local ref indicating a
|
||||
* possible update since checkout; reject the push.
|
||||
* possible update since checkout, then remember the
|
||||
* rejection in case the push is non-fast-forward.
|
||||
*/
|
||||
if (ref->expect_old_sha1) {
|
||||
if (!oideq(&ref->old_oid, &ref->old_oid_expect))
|
||||
reject_reason = REF_STATUS_REJECT_STALE;
|
||||
else if (ref->check_reachable && ref->unreachable)
|
||||
reject_reason =
|
||||
deferred_reject_reason =
|
||||
REF_STATUS_REJECT_REMOTE_UPDATED;
|
||||
else if (ref->check_reachable && ref->unverifiable)
|
||||
deferred_reject_reason =
|
||||
REF_STATUS_REJECT_UNVERIFIABLE;
|
||||
else
|
||||
/*
|
||||
* If the ref isn't stale, and is reachable
|
||||
|
|
@ -1761,6 +1766,14 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
|||
reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
|
||||
}
|
||||
|
||||
/*
|
||||
* If push is non-fast-forward and we were asked to
|
||||
* verify the reflog but were unable to, then reflog
|
||||
* verification is the right reject_reason.
|
||||
*/
|
||||
if (deferred_reject_reason && reject_reason)
|
||||
reject_reason = deferred_reject_reason;
|
||||
|
||||
/*
|
||||
* "--force" will defeat any rejection implemented
|
||||
* by the rules above.
|
||||
|
|
@ -2899,10 +2912,32 @@ cleanup_return:
|
|||
*/
|
||||
static void check_if_includes_upstream(struct ref *remote)
|
||||
{
|
||||
struct ref *local = get_local_ref(remote->name);
|
||||
if (!local)
|
||||
struct ref *local;
|
||||
const char *name;
|
||||
|
||||
/* ref without peer_ref will not be pushed */
|
||||
if (!remote->peer_ref)
|
||||
return;
|
||||
|
||||
/* A deletion has no local history to check against. */
|
||||
if (is_null_oid(&remote->peer_ref->new_oid))
|
||||
return;
|
||||
|
||||
name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
|
||||
remote->peer_ref->name,
|
||||
RESOLVE_REF_READING, NULL, NULL);
|
||||
|
||||
/*
|
||||
* if we resolve the ref to anything other than a branch,
|
||||
* then there is no reliable reflog to check
|
||||
*/
|
||||
if (!name || !starts_with(name, "refs/heads/")) {
|
||||
remote->unverifiable = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
local = get_local_ref(name);
|
||||
|
||||
if (is_reachable_in_reflog(local->name, remote) <= 0)
|
||||
remote->unreachable = 1;
|
||||
free_one_ref(local);
|
||||
|
|
|
|||
10
remote.h
10
remote.h
|
|
@ -171,10 +171,13 @@ struct ref {
|
|||
/* Need to check if local reflog reaches the remote tip. */
|
||||
check_reachable:1,
|
||||
/*
|
||||
* Store the result of the check enabled by "check_reachable";
|
||||
* implies the local reflog does not reach the remote tip.
|
||||
* Store the result of the check enabled by "check_reachable".
|
||||
* "unreachable" implies the local reflog does not reach the remote
|
||||
* tip. "unverifiable" implies no local branch reflog to check; i.e.,
|
||||
* detached HEAD.
|
||||
*/
|
||||
unreachable:1;
|
||||
unreachable:1,
|
||||
unverifiable:1;
|
||||
|
||||
enum {
|
||||
REF_NOT_MATCHED = 0, /* initial value */
|
||||
|
|
@ -205,6 +208,7 @@ struct ref {
|
|||
REF_STATUS_REJECT_STALE,
|
||||
REF_STATUS_REJECT_SHALLOW,
|
||||
REF_STATUS_REJECT_REMOTE_UPDATED,
|
||||
REF_STATUS_REJECT_UNVERIFIABLE,
|
||||
REF_STATUS_UPTODATE,
|
||||
REF_STATUS_REMOTE_REJECT,
|
||||
REF_STATUS_EXPECTING_REPORT,
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar
|
|||
case REF_STATUS_REJECT_NEEDS_FORCE:
|
||||
case REF_STATUS_REJECT_STALE:
|
||||
case REF_STATUS_REJECT_REMOTE_UPDATED:
|
||||
case REF_STATUS_REJECT_UNVERIFIABLE:
|
||||
case REF_STATUS_REJECT_NODELETE:
|
||||
return CHECK_REF_STATUS_REJECTED;
|
||||
case REF_STATUS_UPTODATE:
|
||||
|
|
|
|||
|
|
@ -311,7 +311,8 @@ test_expect_success 'background updates to remote can be mitigated with "--force
|
|||
git switch main &&
|
||||
test_commit J &&
|
||||
git fetch --all &&
|
||||
test_must_fail git push --force-with-lease --force-if-includes --all
|
||||
test_must_fail git push --force-with-lease --force-if-includes --all 2>err &&
|
||||
test_grep "remote ref updated since checkout" err
|
||||
) &&
|
||||
git ls-remote dst refs/heads/main >actual.main &&
|
||||
git ls-remote dst refs/heads/branch >actual.branch &&
|
||||
|
|
@ -414,4 +415,116 @@ test_expect_success '"--force-if-includes" should allow forced update when remot
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should allow forced update when using differently named branches' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch -c newbranch origin/main &&
|
||||
git rebase HEAD --onto HEAD^ &&
|
||||
git push --force-if-includes --force-with-lease origin newbranch:main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should allow forced update from HEAD' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch -c newbranch origin/main &&
|
||||
git rebase HEAD --onto HEAD^ &&
|
||||
git push --force-if-includes --force-with-lease origin HEAD:main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should allow fast-forward push without local reflog' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch main &&
|
||||
git reset --hard origin/main &&
|
||||
test_commit I &&
|
||||
git reflog expire --expire=all --all &&
|
||||
git push --force-with-lease --force-if-includes origin main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should allow fast-forward push from tag' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch -c newbranch origin/main &&
|
||||
test_commit I &&
|
||||
git tag T &&
|
||||
git push --force-with-lease --force-if-includes origin T:main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should reject forced update from differently named branches when local lacks remote ref' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch main &&
|
||||
git reset --hard origin/main &&
|
||||
git switch --orphan orphan &&
|
||||
test_commit I &&
|
||||
test_must_fail git push --force-with-lease --force-if-includes origin orphan:main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should reject forced update from HEAD when it lacks remote ref' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch main &&
|
||||
git reset --hard origin/main &&
|
||||
git switch --orphan orphan &&
|
||||
test_commit I &&
|
||||
test_must_fail git push --force-with-lease --force-if-includes origin HEAD:main
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should reject forced update from detached HEAD' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch main &&
|
||||
git reset --hard origin/main &&
|
||||
git switch -c newbranch origin/main &&
|
||||
git checkout HEAD^ &&
|
||||
test_must_fail git push --force-if-includes --force-with-lease origin HEAD:main 2>err &&
|
||||
test_grep "remote ref unverifiable" err &&
|
||||
test_grep "no-force-if-includes" err
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '"--force-if-includes" should reject forced update from tag' '
|
||||
setup_src_dup_dst &&
|
||||
test_when_finished "rm -fr dst src dup" &&
|
||||
(
|
||||
cd src &&
|
||||
git fetch &&
|
||||
git switch main &&
|
||||
git reset --hard origin/main &&
|
||||
git switch -c newbranch origin/main &&
|
||||
git checkout HEAD^ &&
|
||||
git tag stable &&
|
||||
test_must_fail git push --force-if-includes --force-with-lease origin stable:main 2>err &&
|
||||
test_grep "remote ref unverifiable" err &&
|
||||
test_grep "no-force-if-includes" err
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
|||
|
|
@ -907,6 +907,10 @@ static int push_update_ref_status(struct strbuf *buf,
|
|||
status = REF_STATUS_REJECT_REMOTE_UPDATED;
|
||||
FREE_AND_NULL(msg);
|
||||
}
|
||||
else if (!strcmp(msg, "remote ref unverifiable")) {
|
||||
status = REF_STATUS_REJECT_UNVERIFIABLE;
|
||||
FREE_AND_NULL(msg);
|
||||
}
|
||||
else if (!strcmp(msg, "forced update")) {
|
||||
forced = 1;
|
||||
FREE_AND_NULL(msg);
|
||||
|
|
@ -1060,6 +1064,7 @@ static int push_refs_with_push(struct transport *transport,
|
|||
case REF_STATUS_REJECT_STALE:
|
||||
case REF_STATUS_REJECT_ALREADY_EXISTS:
|
||||
case REF_STATUS_REJECT_REMOTE_UPDATED:
|
||||
case REF_STATUS_REJECT_UNVERIFIABLE:
|
||||
if (atomic) {
|
||||
reject_atomic_push(remote_refs, mirror);
|
||||
string_list_clear(&cas_options, 0);
|
||||
|
|
|
|||
|
|
@ -823,6 +823,11 @@ static int print_one_push_report(struct ref *ref, const char *dest, int count,
|
|||
"remote ref updated since checkout",
|
||||
report, porcelain, summary_width);
|
||||
break;
|
||||
case REF_STATUS_REJECT_UNVERIFIABLE:
|
||||
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
||||
"remote ref unverifiable",
|
||||
report, porcelain, summary_width);
|
||||
break;
|
||||
case REF_STATUS_REJECT_SHALLOW:
|
||||
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
||||
"new shallow roots not allowed",
|
||||
|
|
@ -937,6 +942,8 @@ void transport_print_push_status(const char *dest, struct ref *refs,
|
|||
*reject_reasons |= REJECT_NEEDS_FORCE;
|
||||
} else if (ref->status == REF_STATUS_REJECT_REMOTE_UPDATED) {
|
||||
*reject_reasons |= REJECT_REF_NEEDS_UPDATE;
|
||||
} else if (ref->status == REF_STATUS_REJECT_UNVERIFIABLE) {
|
||||
*reject_reasons |= REJECT_REF_UNVERIFIABLE;
|
||||
}
|
||||
}
|
||||
free(head);
|
||||
|
|
@ -1394,6 +1401,7 @@ static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void
|
|||
switch (r->status) {
|
||||
case REF_STATUS_REJECT_NONFASTFORWARD:
|
||||
case REF_STATUS_REJECT_REMOTE_UPDATED:
|
||||
case REF_STATUS_REJECT_UNVERIFIABLE:
|
||||
case REF_STATUS_REJECT_STALE:
|
||||
case REF_STATUS_UPTODATE:
|
||||
return 0; /* skip refs which won't be pushed */
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
|
|||
#define REJECT_FETCH_FIRST 0x08
|
||||
#define REJECT_NEEDS_FORCE 0x10
|
||||
#define REJECT_REF_NEEDS_UPDATE 0x20
|
||||
#define REJECT_REF_UNVERIFIABLE 0x40
|
||||
|
||||
int transport_push(struct repository *repo,
|
||||
struct transport *connection,
|
||||
|
|
|
|||
Loading…
Reference in New Issue