push: offer a force push after rewriting pushed commits
After amending or rebasing commits you already pushed, "git push" is rejected with a hint to run "git pull" first. Pulling merges the old copies of the same work back in. When the rejected remote tip is a commit your branch pointed to before, according to its reflog, the remote carries no work you have not seen. Name the branch and offer a force push next to the pull: hint: Updates were rejected because 'origin/topic' has diverged hint: from your current branch. Use 'git pull origin topic' hint: to integrate the remote changes, or replace them with hint: 'git push --force-with-lease origin topic'. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>seen
parent
f64b8d316f
commit
ae66f77bf9
|
|
@ -301,6 +301,12 @@ static const char message_advice_pull_from_branch_before_push[] =
|
|||
"from your current branch. Use 'git pull %s %s'\n"
|
||||
"to integrate the remote changes.");
|
||||
|
||||
static const char message_advice_pull_or_force_before_push[] =
|
||||
N_("Updates were rejected because '%s' has diverged\n"
|
||||
"from your current branch. Use 'git pull %s %s'\n"
|
||||
"to integrate the remote changes, or replace them with\n"
|
||||
"'git push --force-with-lease %s %s'.");
|
||||
|
||||
static const char message_advice_checkout_pull_push[] =
|
||||
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
|
||||
"counterpart. If you want to integrate the remote changes, use 'git pull'\n"
|
||||
|
|
@ -328,7 +334,8 @@ 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 void advise_pull_before_push(struct remote *push_remote)
|
||||
static void advise_pull_before_push(struct remote *push_remote,
|
||||
unsigned int reject_reasons)
|
||||
{
|
||||
struct branch *branch = branch_get(NULL);
|
||||
struct remote *remote = NULL;
|
||||
|
|
@ -349,7 +356,11 @@ static void advise_pull_before_push(struct remote *push_remote)
|
|||
tracking_name = refs_shorten_unambiguous_ref(
|
||||
get_main_ref_store(the_repository), tracking, 0);
|
||||
|
||||
if (tracking && (!upstream || strcmp(tracking, upstream)))
|
||||
if (tracking && (reject_reasons & REJECT_NON_FF_HEAD_REWRITE))
|
||||
advise(_(message_advice_pull_or_force_before_push),
|
||||
tracking_name, remote->name, branch->name,
|
||||
remote->name, branch->name);
|
||||
else if (tracking && (!upstream || strcmp(tracking, upstream)))
|
||||
advise(_(message_advice_pull_from_branch_before_push),
|
||||
tracking_name, remote->name, branch->name);
|
||||
else
|
||||
|
|
@ -435,7 +446,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
|
|||
return 0;
|
||||
|
||||
if (reject_reasons & REJECT_NON_FF_HEAD) {
|
||||
advise_pull_before_push(remote);
|
||||
advise_pull_before_push(remote, reject_reasons);
|
||||
} else if (reject_reasons & REJECT_NON_FF_OTHER) {
|
||||
advise_checkout_pull_push();
|
||||
} else if (reject_reasons & REJECT_ALREADY_EXISTS) {
|
||||
|
|
|
|||
|
|
@ -863,6 +863,24 @@ test_expect_success 'push to the upstream branch' '
|
|||
hint: use ${SQ}git pull${SQ} before pushing again.
|
||||
hint: See the ${SQ}Note about fast-forwards${SQ} in ${SQ}git push --help${SQ} for details.
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
(
|
||||
cd test &&
|
||||
git pull --rebase &&
|
||||
git push &&
|
||||
echo amended >mine21 &&
|
||||
git commit -a --amend --no-edit &&
|
||||
test_must_fail git push 2>../actual
|
||||
) &&
|
||||
cat >expect <<-EOF &&
|
||||
To $url
|
||||
! [rejected] feature21 -> feature21 (non-fast-forward)
|
||||
error: failed to push some refs to ${SQ}$url${SQ}
|
||||
hint: Updates were rejected because ${SQ}origin/feature21${SQ} has diverged
|
||||
hint: from your current branch. Use ${SQ}git pull origin feature21${SQ}
|
||||
hint: to integrate the remote changes, or replace them with
|
||||
hint: ${SQ}git push --force-with-lease origin feature21${SQ}.
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
|
|
|
|||
42
transport.c
42
transport.c
|
|
@ -891,6 +891,42 @@ int transport_summary_width(const struct ref *refs)
|
|||
return (2 * maxw + 3);
|
||||
}
|
||||
|
||||
struct reflog_has_tip_cb_data {
|
||||
const struct object_id *target;
|
||||
int found;
|
||||
};
|
||||
|
||||
static int reflog_has_tip(const char *refname UNUSED,
|
||||
struct object_id *old_oid UNUSED,
|
||||
struct object_id *new_oid,
|
||||
const char *committer UNUSED,
|
||||
timestamp_t timestamp UNUSED,
|
||||
int tz UNUSED, const char *msg UNUSED,
|
||||
void *cb_data)
|
||||
{
|
||||
struct reflog_has_tip_cb_data *cb = cb_data;
|
||||
|
||||
if (!oideq(new_oid, cb->target))
|
||||
return 0;
|
||||
cb->found = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Was "refname" ever at "oid" according to its reflog? Then a remote
|
||||
* sitting at "oid" carries no work we have not seen, only commits we
|
||||
* have since rewritten, for example with 'commit --amend' or 'rebase'.
|
||||
*/
|
||||
static int local_ref_used_to_be_at(const char *refname,
|
||||
const struct object_id *oid)
|
||||
{
|
||||
struct reflog_has_tip_cb_data cb = { .target = oid };
|
||||
|
||||
refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
|
||||
refname, reflog_has_tip, &cb);
|
||||
return cb.found;
|
||||
}
|
||||
|
||||
void transport_print_push_status(const char *dest, struct ref *refs,
|
||||
int verbose, int porcelain, unsigned int *reject_reasons)
|
||||
{
|
||||
|
|
@ -925,9 +961,11 @@ void transport_print_push_status(const char *dest, struct ref *refs,
|
|||
n += print_one_push_status(ref, dest, n,
|
||||
porcelain, summary_width);
|
||||
if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {
|
||||
if (head != NULL && !strcmp(head, ref->name))
|
||||
if (head != NULL && !strcmp(head, ref->name)) {
|
||||
*reject_reasons |= REJECT_NON_FF_HEAD;
|
||||
else
|
||||
if (local_ref_used_to_be_at(head, &ref->old_oid))
|
||||
*reject_reasons |= REJECT_NON_FF_HEAD_REWRITE;
|
||||
} else
|
||||
*reject_reasons |= REJECT_NON_FF_OTHER;
|
||||
} else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {
|
||||
*reject_reasons |= REJECT_ALREADY_EXISTS;
|
||||
|
|
|
|||
13
transport.h
13
transport.h
|
|
@ -252,12 +252,13 @@ 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_REF_NEEDS_UPDATE 0x20
|
||||
#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
|
||||
#define REJECT_NON_FF_HEAD_REWRITE 0x40
|
||||
|
||||
int transport_push(struct repository *repo,
|
||||
struct transport *connection,
|
||||
|
|
|
|||
Loading…
Reference in New Issue