enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ ENABLE_ADVICE_PULL = (1 << 0),
+ ENABLE_ADVICE_PUSH = (1 << 1),
+ ENABLE_ADVICE_DIVERGENCE = (1 << 2),
+};
+
struct counted_string {
size_t len;
const char *s;
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_remote_push_branch(struct branch *branch)
+{
+ struct remote *remote;
+ const char *push_remote;
+ char *push_dst = NULL;
+ char *tracking_ref;
+ const char *resolved;
+ char *ret;
+
+ if (!branch)
+ return NULL;
+
+ push_remote = pushremote_for_branch(branch, NULL);
+ if (!push_remote)
+ return NULL;
+
+ remote = remotes_remote_get(the_repository, push_remote);
+ if (!remote)
+ return NULL;
+
+ push_dst = remote_ref_for_branch(branch, 1);
+ if (!push_dst) {
+ if (remote->push.nr)
+ return NULL;
+ push_dst = xstrdup(branch->refname);
+ }
+
+ tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+ free(push_dst);
+
+ if (!tracking_ref)
+ return NULL;
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ tracking_ref,
+ RESOLVE_REF_READING,
+ NULL, NULL);
+
+ if (!resolved) {
+ free(tracking_ref);
+ return NULL;
+ }
+
+ ret = xstrdup(resolved);
+ free(tracking_ref);
+ return ret;
+}
+
static void format_branch_comparison(struct strbuf *sb,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
- bool show_divergence_advice)
+ unsigned flags)
{
+ bool enable_push_advice = (flags & ENABLE_ADVICE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool enable_pull_advice = (flags & ENABLE_ADVICE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool enable_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
branch_name);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (enable_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
"Your branch is ahead of '%s' by %d commits.\n",
ours),
branch_name, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (enable_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (enable_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (enable_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_flags = ENABLE_ADVICE_PULL | ENABLE_ADVICE_PUSH;
+ int push_ours, push_theirs, push_cmp_fetch;
+ char *full_push;
+ char *push = NULL;
+ unsigned push_branch_flags = 0;
cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (cmp_fetch < 0) {
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+ full_push = get_remote_push_branch(branch);
+ if (full_push) {
+ push = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_push, 0);
+ if (push && base && strcmp(base, push)) {
+ push_cmp_fetch = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_cmp_fetch >= 0) {
+ base_branch_flags = ENABLE_ADVICE_PULL;
+ push_branch_flags = ENABLE_ADVICE_PUSH;
+ }
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else {
- format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ if (show_divergence_advice)
+ base_branch_flags |= ENABLE_ADVICE_DIVERGENCE;
+ format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf,
+ base_branch_flags);
+ }
+
+ if (push_branch_flags & ENABLE_ADVICE_PUSH) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, !push_cmp_fetch, push_ours, push_theirs, push, abf,
+ push_branch_flags);
}
free(base);
+ free(full_push);
+ free(push);
return 1;
}
test_cmp expect actual
'
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch b4
+ Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 &&
+ git status --no-ahead-behind >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch b4
+ Your branch and ${SQ}origin/main${SQ} refer to different commits.
+ (use "git status --ahead-behind" for details)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status shows ahead of both origin/main and feature branch' '
+ (
+ cd test &&
+ git checkout -b feature2 origin/main &&
+ git push origin HEAD &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature2
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+ Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
+ (
+ cd test &&
+ git checkout feature2 >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+ Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout -b feature4 origin/main &&
+ advance work1 &&
+ git checkout origin/main &&
+ advance work2 &&
+ git push origin HEAD:main &&
+ git checkout feature4 &&
+ advance work3
+ )
+'
+
+test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
+ (
+ cd test &&
+ git checkout feature4 &&
+ git branch --set-upstream-to origin/main &&
+ git push origin HEAD &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature4
+ Your branch and ${SQ}origin/main${SQ} have diverged,
+ and have 3 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+ Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind shows diverged from origin/main and ahead of feature branch' '
+ (
+ cd test &&
+ git checkout feature4 &&
+ git status --no-ahead-behind >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature4
+ Your branch and ${SQ}origin/main${SQ} refer to different commits.
+
+ Your branch and ${SQ}origin/feature4${SQ} refer to different commits.
+ (use "git status --ahead-behind" for details)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git config remote.pushDefault origin
+ )
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin' '
+ (
+ cd test &&
+ git checkout -b feature5 upstream/main &&
+ git push origin &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature5
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+ Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
+ (
+ cd test &&
+ git checkout -b feature6 upstream/main &&
+ advance work &&
+ git push origin &&
+ git reset --hard upstream/main &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature6
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+ Your branch and ${SQ}origin/feature6${SQ} have diverged,
+ and have 1 and 1 different commits each, respectively.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push branch up to date' '
+ (
+ cd test &&
+ git checkout -b feature7 upstream/main &&
+ git push origin &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature7
+ Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+ Your branch is up to date with ${SQ}origin/feature7${SQ}.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind with upstream remote and push branch up to date' '
+ (
+ cd test &&
+ git checkout feature7 &&
+ git push origin &&
+ git status --no-ahead-behind >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature7
+ Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+ Your branch is up to date with ${SQ}origin/feature7${SQ}.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows push branch up to date' '
+ (
+ cd test &&
+ git checkout feature7 >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+ Your branch is up to date with ${SQ}origin/feature7${SQ}.
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with upstream ahead and push branch up to date' '
+ (
+ cd test &&
+ git checkout -b ahead upstream/main &&
+ advance work &&
+ git push upstream HEAD &&
+ git checkout -b feature8 upstream/main &&
+ git push origin &&
+ git branch --set-upstream-to upstream/ahead &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature8
+ Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+ Your branch is up to date with ${SQ}origin/feature8${SQ}.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status shows remapped push refspec' '
+ (
+ cd test &&
+ git checkout -b feature9 origin/main &&
+ git config remote.origin.push refs/heads/feature9:refs/heads/remapped &&
+ git push &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature9
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+ Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status shows remapped push refspec with upstream remote' '
+ (
+ cd test &&
+ git checkout -b feature10 upstream/main &&
+ git config remote.origin.push refs/heads/feature10:refs/heads/remapped &&
+ git push origin &&
+ advance work &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch feature10
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+ Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
test_done