Merge branch 'hn/status-push-rebased-on-newer-upstream' into seen
The 'git status' output and 'git push' rejection advice have been improved to recognize when a pushed branch was cleanly rebased onto a newer upstream, and now correctly suggest force-pushing instead of pulling. * hn/status-push-rebased-on-newer-upstream: push: suggest a force push after a clean rebase push: offer a force push after rewriting pushed commits push: name the branch to pull from when it is not the upstream status: suggest a force push after a clean rebase status: say when the push branch was rebased cleanly status: count push divergence outside the upstreamseen
commit
a7083008cd
|
|
@ -12,6 +12,7 @@
|
|||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#include "hex.h"
|
||||
#include "refs.h"
|
||||
#include "refspec.h"
|
||||
#include "run-command.h"
|
||||
#include "remote.h"
|
||||
|
|
@ -295,6 +296,22 @@ static const char message_advice_pull_before_push[] =
|
|||
"use 'git pull' before pushing again.\n"
|
||||
"See the 'Note about fast-forwards' in 'git push --help' for details.");
|
||||
|
||||
static const char message_advice_pull_from_branch_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.");
|
||||
|
||||
static const char message_advice_force_after_clean_rebase[] =
|
||||
N_("Updates were rejected because '%s' has diverged\n"
|
||||
"from your current branch, which was rebased cleanly on '%s'.\n"
|
||||
"Use 'git push --force-with-lease %s %s' to replace it.");
|
||||
|
||||
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"
|
||||
|
|
@ -329,11 +346,49 @@ static const char message_advice_ref_unverifiable[] =
|
|||
"'--force-with-lease=<ref>:<expect>' or use '--no-force-if-includes'\n"
|
||||
"to skip this check.");
|
||||
|
||||
static void advise_pull_before_push(void)
|
||||
static void advise_pull_before_push(struct remote *push_remote,
|
||||
unsigned int reject_reasons)
|
||||
{
|
||||
struct branch *branch = branch_get(NULL);
|
||||
struct remote *remote = NULL;
|
||||
const char *upstream = NULL;
|
||||
char *tracking = NULL;
|
||||
char *tracking_name = NULL;
|
||||
|
||||
if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
|
||||
return;
|
||||
advise(_(message_advice_pull_before_push));
|
||||
|
||||
if (branch) {
|
||||
remote = repo_remote_for_push_tracking(the_repository,
|
||||
push_remote);
|
||||
tracking = apply_refspecs(&remote->fetch, branch->refname);
|
||||
upstream = branch_get_upstream(branch, NULL);
|
||||
}
|
||||
if (tracking)
|
||||
tracking_name = refs_shorten_unambiguous_ref(
|
||||
get_main_ref_store(the_repository), tracking, 0);
|
||||
|
||||
if (tracking && branch_rebased_cleanly(branch, tracking)) {
|
||||
char *upstream_name = refs_shorten_unambiguous_ref(
|
||||
get_main_ref_store(the_repository), upstream, 0);
|
||||
|
||||
advise(_(message_advice_force_after_clean_rebase),
|
||||
tracking_name, upstream_name,
|
||||
remote->name, branch->name);
|
||||
free(upstream_name);
|
||||
} else 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 {
|
||||
advise(_(message_advice_pull_before_push));
|
||||
}
|
||||
|
||||
free(tracking_name);
|
||||
free(tracking);
|
||||
}
|
||||
|
||||
static void advise_checkout_pull_push(void)
|
||||
|
|
@ -385,6 +440,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
|
|||
int err;
|
||||
unsigned int reject_reasons;
|
||||
char *anon_url = transport_anonymize_url(transport->url);
|
||||
struct remote *remote = transport->remote;
|
||||
|
||||
transport_set_verbosity(transport, verbosity, progress);
|
||||
transport->family = family;
|
||||
|
|
@ -419,7 +475,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();
|
||||
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) {
|
||||
|
|
|
|||
132
remote.c
132
remote.c
|
|
@ -2267,11 +2267,12 @@ int resolve_remote_symref(struct ref *ref, struct ref *list)
|
|||
*/
|
||||
|
||||
static int stat_branch_pair(const char *branch_name, const char *base,
|
||||
int *num_ours, int *num_theirs,
|
||||
const char *exclude,
|
||||
int *num_ours, int *num_theirs, int *num_same,
|
||||
enum ahead_behind_flags abf)
|
||||
{
|
||||
struct object_id oid;
|
||||
struct commit *ours, *theirs;
|
||||
struct commit *ours, *theirs, *excluded = NULL;
|
||||
struct rev_info revs;
|
||||
struct strvec argv = STRVEC_INIT;
|
||||
|
||||
|
|
@ -2288,7 +2289,17 @@ static int stat_branch_pair(const char *branch_name, const char *base,
|
|||
if (!ours)
|
||||
return -1;
|
||||
|
||||
if (exclude) {
|
||||
if (refs_read_ref(get_main_ref_store(the_repository), exclude, &oid))
|
||||
return -1;
|
||||
excluded = lookup_commit_reference(the_repository, &oid);
|
||||
if (!excluded)
|
||||
return -1;
|
||||
}
|
||||
|
||||
*num_theirs = *num_ours = 0;
|
||||
if (num_same)
|
||||
*num_same = 0;
|
||||
|
||||
/* are we the same? */
|
||||
if (theirs == ours)
|
||||
|
|
@ -2301,9 +2312,13 @@ static int stat_branch_pair(const char *branch_name, const char *base,
|
|||
/* Run "rev-list --left-right ours...theirs" internally... */
|
||||
strvec_push(&argv, ""); /* ignored */
|
||||
strvec_push(&argv, "--left-right");
|
||||
if (num_same)
|
||||
strvec_push(&argv, "--cherry-mark");
|
||||
strvec_pushf(&argv, "%s...%s",
|
||||
oid_to_hex(&ours->object.oid),
|
||||
oid_to_hex(&theirs->object.oid));
|
||||
if (excluded)
|
||||
strvec_pushf(&argv, "^%s", oid_to_hex(&excluded->object.oid));
|
||||
strvec_push(&argv, "--");
|
||||
|
||||
repo_init_revisions(the_repository, &revs, NULL);
|
||||
|
|
@ -2320,11 +2335,15 @@ static int stat_branch_pair(const char *branch_name, const char *base,
|
|||
(*num_ours)++;
|
||||
else
|
||||
(*num_theirs)++;
|
||||
if (num_same && (c->object.flags & PATCHSAME))
|
||||
(*num_same)++;
|
||||
}
|
||||
|
||||
/* clear object flags smudged by the above traversal */
|
||||
clear_commit_marks(ours, ALL_REV_FLAGS);
|
||||
clear_commit_marks(theirs, ALL_REV_FLAGS);
|
||||
if (excluded)
|
||||
clear_commit_marks(excluded, ALL_REV_FLAGS);
|
||||
|
||||
strvec_clear(&argv);
|
||||
release_revisions(&revs);
|
||||
|
|
@ -2364,7 +2383,49 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
|
|||
if (!base)
|
||||
return -1;
|
||||
|
||||
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
|
||||
return stat_branch_pair(branch->refname, base, NULL,
|
||||
num_ours, num_theirs, NULL, abf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Count the commits that differ between branch_name and base but are not
|
||||
* in upstream. Return false when they cannot be counted or upstream
|
||||
* accounts for none of the ours and theirs commits. Otherwise set
|
||||
* *same_changes when the remaining commits carry the same changes on
|
||||
* both sides.
|
||||
*/
|
||||
static bool stat_outside_upstream(const char *branch_name, const char *base,
|
||||
const char *upstream, int ours, int theirs,
|
||||
int *ours_unmerged, int *theirs_unmerged,
|
||||
bool *same_changes)
|
||||
{
|
||||
int same;
|
||||
|
||||
if (stat_branch_pair(branch_name, base, upstream, ours_unmerged,
|
||||
theirs_unmerged, &same, AHEAD_BEHIND_FULL) < 0)
|
||||
return false;
|
||||
if (*ours_unmerged == ours && *theirs_unmerged == theirs)
|
||||
return false;
|
||||
*same_changes = *ours_unmerged &&
|
||||
same == *ours_unmerged + *theirs_unmerged;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool branch_rebased_cleanly(struct branch *branch, const char *base)
|
||||
{
|
||||
const char *upstream = branch_get_upstream(branch, NULL);
|
||||
int ours, theirs, ours_unmerged, theirs_unmerged;
|
||||
bool same_changes = false;
|
||||
|
||||
if (!upstream || !strcmp(upstream, base))
|
||||
return false;
|
||||
if (stat_branch_pair(branch->refname, base, NULL, &ours, &theirs,
|
||||
NULL, AHEAD_BEHIND_FULL) <= 0 || !ours || !theirs)
|
||||
return false;
|
||||
return stat_outside_upstream(branch->refname, base, upstream,
|
||||
ours, theirs, &ours_unmerged,
|
||||
&theirs_unmerged, &same_changes) &&
|
||||
same_changes;
|
||||
}
|
||||
|
||||
static char *resolve_compare_branch(struct branch *branch, const char *name)
|
||||
|
|
@ -2396,6 +2457,9 @@ static void format_branch_comparison(struct strbuf *sb,
|
|||
const char *branch_name,
|
||||
const char *push_remote_name,
|
||||
const char *push_branch_name,
|
||||
const char *upstream_name,
|
||||
int ours_unmerged, int theirs_unmerged,
|
||||
bool same_changes,
|
||||
enum ahead_behind_flags abf,
|
||||
unsigned flags)
|
||||
{
|
||||
|
|
@ -2441,15 +2505,40 @@ static void format_branch_comparison(struct strbuf *sb,
|
|||
_(" (use \"git pull\" to update your local branch)\n"));
|
||||
}
|
||||
} else {
|
||||
strbuf_addf(sb,
|
||||
Q_("Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commit each, "
|
||||
"respectively.\n",
|
||||
"Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commits each, "
|
||||
"respectively.\n",
|
||||
ours + theirs),
|
||||
branch_name, ours, theirs);
|
||||
if (same_changes) {
|
||||
strbuf_addf(sb,
|
||||
Q_("Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commit each "
|
||||
"(rebased cleanly on '%s').\n",
|
||||
"Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commits each "
|
||||
"(rebased cleanly on '%s').\n",
|
||||
ours + theirs),
|
||||
branch_name, ours, theirs, upstream_name);
|
||||
if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
|
||||
strbuf_addstr(sb,
|
||||
_(" (use \"git push --force-with-lease\" to publish your local commits)\n"));
|
||||
} else if (upstream_name)
|
||||
strbuf_addf(sb,
|
||||
Q_("Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commit each "
|
||||
"(%d and %d not in '%s').\n",
|
||||
"Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commits each "
|
||||
"(%d and %d not in '%s').\n",
|
||||
ours + theirs),
|
||||
branch_name, ours, theirs,
|
||||
ours_unmerged, theirs_unmerged, upstream_name);
|
||||
else
|
||||
strbuf_addf(sb,
|
||||
Q_("Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commit each, "
|
||||
"respectively.\n",
|
||||
"Your branch and '%s' have diverged,\n"
|
||||
"and have %d and %d different commits each, "
|
||||
"respectively.\n",
|
||||
ours + theirs),
|
||||
branch_name, ours, theirs);
|
||||
if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
|
||||
if (push_remote_name && push_branch_name)
|
||||
strbuf_addf(sb,
|
||||
|
|
@ -2493,7 +2582,10 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
|
|||
for (i = 0; i < branches.nr; i++) {
|
||||
char *full_ref;
|
||||
char *short_ref;
|
||||
char *upstream_name = NULL;
|
||||
int ours, theirs, cmp;
|
||||
int ours_unmerged = 0, theirs_unmerged = 0;
|
||||
bool same_changes = false;
|
||||
int is_upstream, is_push;
|
||||
unsigned flags = 0;
|
||||
const char *push_remote_name = NULL;
|
||||
|
|
@ -2518,8 +2610,17 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
|
|||
if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
|
||||
is_push = 1;
|
||||
|
||||
cmp = stat_branch_pair(branch->refname, full_ref,
|
||||
&ours, &theirs, abf);
|
||||
cmp = stat_branch_pair(branch->refname, full_ref, NULL,
|
||||
&ours, &theirs, NULL, abf);
|
||||
|
||||
if (cmp > 0 && ours && theirs && upstream_ref && !is_upstream &&
|
||||
stat_outside_upstream(branch->refname, full_ref,
|
||||
upstream_ref, ours, theirs,
|
||||
&ours_unmerged, &theirs_unmerged,
|
||||
&same_changes))
|
||||
upstream_name = refs_shorten_unambiguous_ref(
|
||||
get_main_ref_store(the_repository),
|
||||
upstream_ref, 0);
|
||||
|
||||
if (cmp < 0) {
|
||||
if (is_upstream) {
|
||||
|
|
@ -2562,11 +2663,14 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
|
|||
}
|
||||
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
|
||||
push_remote_name, push_branch_name,
|
||||
upstream_name, ours_unmerged,
|
||||
theirs_unmerged, same_changes,
|
||||
abf, flags);
|
||||
reported = 1;
|
||||
|
||||
free(full_ref);
|
||||
free(short_ref);
|
||||
free(upstream_name);
|
||||
}
|
||||
|
||||
string_list_clear(&branches, 0);
|
||||
|
|
|
|||
6
remote.h
6
remote.h
|
|
@ -412,6 +412,12 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
|
|||
enum ahead_behind_flags abf,
|
||||
int show_divergence_advice);
|
||||
|
||||
/*
|
||||
* Return true when the branch has diverged from base only because the
|
||||
* work on base was rebased cleanly on the upstream of the branch.
|
||||
*/
|
||||
bool branch_rebased_cleanly(struct branch *branch, const char *base);
|
||||
|
||||
struct ref *get_local_heads(void);
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -746,4 +746,167 @@ test_expect_success 'status.compareBranches suppresses advice when push tracking
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'status.compareBranches counts push divergence outside upstream' '
|
||||
test_config -C test push.default current &&
|
||||
test_config -C test status.compareBranches "@{upstream} @{push}" &&
|
||||
(
|
||||
cd test &&
|
||||
git checkout -b feature18 origin/main &&
|
||||
advance work18 &&
|
||||
git push
|
||||
) &&
|
||||
git checkout main &&
|
||||
advance main18a &&
|
||||
advance main18b &&
|
||||
git checkout - &&
|
||||
(
|
||||
cd test &&
|
||||
echo amended >work18 &&
|
||||
git commit -a --amend --no-edit &&
|
||||
git pull --rebase &&
|
||||
git status >../actual
|
||||
) &&
|
||||
cat >expect <<-EOF &&
|
||||
On branch feature18
|
||||
Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
|
||||
|
||||
Your branch and ${SQ}origin/feature18${SQ} have diverged,
|
||||
and have 3 and 1 different commits each (1 and 1 not in ${SQ}origin/main${SQ}).
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'status.compareBranches after a clean rebase of the push branch' '
|
||||
test_config -C test push.default current &&
|
||||
test_config -C test status.compareBranches "@{upstream} @{push}" &&
|
||||
(
|
||||
cd test &&
|
||||
git checkout -b feature19 origin/main &&
|
||||
advance work19 &&
|
||||
git push
|
||||
) &&
|
||||
git checkout main &&
|
||||
advance main19a &&
|
||||
advance main19b &&
|
||||
git checkout - &&
|
||||
(
|
||||
cd test &&
|
||||
git pull --rebase &&
|
||||
git status >../actual
|
||||
) &&
|
||||
cat >expect <<-EOF &&
|
||||
On branch feature19
|
||||
Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
|
||||
|
||||
Your branch and ${SQ}origin/feature19${SQ} have diverged,
|
||||
and have 3 and 1 different commits each (rebased cleanly on ${SQ}origin/main${SQ}).
|
||||
(use "git push --force-with-lease" to publish your local commits)
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
test_cmp expect actual &&
|
||||
(
|
||||
cd test &&
|
||||
test_must_fail git push 2>../push.err &&
|
||||
git push --force-with-lease origin feature19 &&
|
||||
git status >../actual
|
||||
) &&
|
||||
url=$(git -C test config remote.origin.url) &&
|
||||
cat >expect <<-EOF &&
|
||||
To $url
|
||||
! [rejected] feature19 -> feature19 (non-fast-forward)
|
||||
error: failed to push some refs to ${SQ}$url${SQ}
|
||||
hint: Updates were rejected because ${SQ}origin/feature19${SQ} has diverged
|
||||
hint: from your current branch, which was rebased cleanly on ${SQ}origin/main${SQ}.
|
||||
hint: Use ${SQ}git push --force-with-lease origin feature19${SQ} to replace it.
|
||||
EOF
|
||||
test_cmp expect push.err &&
|
||||
cat >expect <<-EOF &&
|
||||
On branch feature19
|
||||
Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
|
||||
|
||||
Your branch is up to date with ${SQ}origin/feature19${SQ}.
|
||||
|
||||
nothing to commit, working tree clean
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'push to a push branch someone else updated suggests pulling from it' '
|
||||
(
|
||||
cd test &&
|
||||
git checkout -b feature20 origin/main &&
|
||||
advance work20 &&
|
||||
git push origin feature20
|
||||
) &&
|
||||
git checkout feature20 &&
|
||||
advance other20 &&
|
||||
git checkout - &&
|
||||
(
|
||||
cd test &&
|
||||
advance mine20 &&
|
||||
git fetch &&
|
||||
test_must_fail git push origin feature20 2>../actual
|
||||
) &&
|
||||
url=$(git -C test config remote.origin.url) &&
|
||||
cat >expect <<-EOF &&
|
||||
To $url
|
||||
! [rejected] feature20 -> feature20 (non-fast-forward)
|
||||
error: failed to push some refs to ${SQ}$url${SQ}
|
||||
hint: Updates were rejected because ${SQ}origin/feature20${SQ} has diverged
|
||||
hint: from your current branch. Use ${SQ}git pull origin feature20${SQ}
|
||||
hint: to integrate the remote changes.
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'push to the upstream branch' '
|
||||
(
|
||||
cd test &&
|
||||
git checkout -b feature21 origin/main &&
|
||||
advance work21 &&
|
||||
git push -u origin feature21
|
||||
) &&
|
||||
git checkout feature21 &&
|
||||
advance other21 &&
|
||||
git checkout - &&
|
||||
(
|
||||
cd test &&
|
||||
advance mine21 &&
|
||||
git fetch &&
|
||||
test_must_fail git push 2>../actual
|
||||
) &&
|
||||
url=$(git -C test config remote.origin.url) &&
|
||||
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 the tip of your current branch is behind
|
||||
hint: its remote counterpart. If you want to integrate the remote changes,
|
||||
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
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
|||
42
transport.c
42
transport.c
|
|
@ -896,6 +896,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)
|
||||
{
|
||||
|
|
@ -930,9 +966,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;
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
|
|||
#define REJECT_NEEDS_FORCE 0x10
|
||||
#define REJECT_REF_NEEDS_UPDATE 0x20
|
||||
#define REJECT_REF_UNVERIFIABLE 0x40
|
||||
#define REJECT_NON_FF_HEAD_REWRITE 0x80
|
||||
|
||||
int transport_push(struct repository *repo,
|
||||
struct transport *connection,
|
||||
|
|
|
|||
Loading…
Reference in New Issue