Skip to content

Commit

Permalink
Merge branch 'no-ahead-behind-v5'
Browse files Browse the repository at this point in the history
Especially in huge code bases with fast-moving `master`, it can be
prohibitively expensive to calculate whether an upstream branch of
a local branch is ahead, behind or diverged.

This topic branch introduces a set of flags to avoid that computation
when we're not even interested in it to begin with.

This merge commit takes the feature early, therefore it is marked
experimental.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
  • Loading branch information
dscho committed Jan 10, 2018
2 parents 01cd253 + 348108d commit f80bd10
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 35 deletions.
6 changes: 6 additions & 0 deletions Documentation/git-status.txt
Expand Up @@ -137,6 +137,12 @@ ignored, then the directory is not shown, but all contents are shown.
update it afterwards if any changes were detected. Defaults to update it afterwards if any changes were detected. Defaults to
`--lock-index`. `--lock-index`.


--ahead-behind::
--no-ahead-behind::
EXPERIMENTAL, Display or do not display detailed ahead/behind
counts for the branch relative to its upstream branch. Defaults
to true.

<pathspec>...:: <pathspec>...::
See the 'pathspec' entry in linkgit:gitglossary[7]. See the 'pathspec' entry in linkgit:gitglossary[7].


Expand Down
2 changes: 1 addition & 1 deletion builtin/checkout.c
Expand Up @@ -607,7 +607,7 @@ static void report_tracking(struct branch_info *new)
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
struct branch *branch = branch_get(new->name); struct branch *branch = branch_get(new->name);


if (!format_tracking_info(branch, &sb)) if (!format_tracking_info(branch, &sb, AHEAD_BEHIND_FULL))
return; return;
fputs(sb.buf, stdout); fputs(sb.buf, stdout);
strbuf_release(&sb); strbuf_release(&sb);
Expand Down
7 changes: 7 additions & 0 deletions builtin/commit.c
Expand Up @@ -1150,6 +1150,9 @@ static void finalize_deferred_config(struct wt_status *s)
s->show_branch = status_deferred_config.show_branch; s->show_branch = status_deferred_config.show_branch;
if (s->show_branch < 0) if (s->show_branch < 0)
s->show_branch = 0; s->show_branch = 0;

if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
} }


static int parse_and_validate_options(int argc, const char *argv[], static int parse_and_validate_options(int argc, const char *argv[],
Expand Down Expand Up @@ -1366,6 +1369,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
N_("show branch information")), N_("show branch information")),
OPT_BOOL(0, "show-stash", &s.show_stash, OPT_BOOL(0, "show-stash", &s.show_stash,
N_("show stash information")), N_("show stash information")),
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
N_("compute full ahead/behind values (EXPERIMENTAL)")),
{ OPTION_CALLBACK, 0, "porcelain", &status_format, { OPTION_CALLBACK, 0, "porcelain", &status_format,
N_("version"), N_("machine-readable output"), N_("version"), N_("machine-readable output"),
PARSE_OPT_OPTARG, opt_parse_porcelain }, PARSE_OPT_OPTARG, opt_parse_porcelain },
Expand Down Expand Up @@ -1669,6 +1674,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT), STATUS_FORMAT_SHORT),
OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
N_("compute full ahead/behind values (EXPERIMENTAL)")),
OPT_SET_INT(0, "porcelain", &status_format, OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format, OPT_SET_INT(0, "long", &status_format,
Expand Down
8 changes: 4 additions & 4 deletions ref-filter.c
Expand Up @@ -1249,8 +1249,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
if (atom->u.remote_ref.option == RR_REF) if (atom->u.remote_ref.option == RR_REF)
*s = show_ref(&atom->u.remote_ref.refname, refname); *s = show_ref(&atom->u.remote_ref.refname, refname);
else if (atom->u.remote_ref.option == RR_TRACK) { else if (atom->u.remote_ref.option == RR_TRACK) {
if (stat_tracking_info(branch, &num_ours, if (stat_tracking_info(branch, &num_ours, &num_theirs,
&num_theirs, NULL)) { NULL, AHEAD_BEHIND_FULL) < 0) {
*s = xstrdup(msgs.gone); *s = xstrdup(msgs.gone);
} else if (!num_ours && !num_theirs) } else if (!num_ours && !num_theirs)
*s = ""; *s = "";
Expand All @@ -1267,8 +1267,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
free((void *)to_free); free((void *)to_free);
} }
} else if (atom->u.remote_ref.option == RR_TRACKSHORT) { } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
if (stat_tracking_info(branch, &num_ours, if (stat_tracking_info(branch, &num_ours, &num_theirs,
&num_theirs, NULL)) NULL, AHEAD_BEHIND_FULL) < 0)
return; return;


if (!num_ours && !num_theirs) if (!num_ours && !num_theirs)
Expand Down
50 changes: 34 additions & 16 deletions remote.c
Expand Up @@ -2007,16 +2007,23 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
} }


/* /*
* Compare a branch with its upstream, and save their differences (number * Lookup the upstream branch for the given branch and if present, optionally
* of commits) in *num_ours and *num_theirs. The name of the upstream branch * compute the commit ahead/behind values for the pair.
* (or NULL if no upstream is defined) is returned via *upstream_name, if it *
* is not itself NULL. * If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the
* counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip
* the (potentially expensive) a/b computation (*num_ours and *num_theirs are
* set to zero).
*
* The name of the upstream branch (or NULL if no upstream is defined) is
* returned via *upstream_name, if it is not itself NULL.
* *
* Returns -1 if num_ours and num_theirs could not be filled in (e.g., no * Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
* upstream defined, or ref does not exist), 0 otherwise. * upstream defined, or ref does not exist). Returns 0 if the commits are
* identical. Returns 1 if commits are different.
*/ */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **upstream_name) const char **upstream_name, enum ahead_behind_flags abf)
{ {
struct object_id oid; struct object_id oid;
struct commit *ours, *theirs; struct commit *ours, *theirs;
Expand Down Expand Up @@ -2044,11 +2051,15 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
if (!ours) if (!ours)
return -1; return -1;


*num_theirs = *num_ours = 0;

/* are we the same? */ /* are we the same? */
if (theirs == ours) { if (theirs == ours)
*num_theirs = *num_ours = 0;
return 0; return 0;
} if (abf == AHEAD_BEHIND_QUICK)
return 1;
if (abf != AHEAD_BEHIND_FULL)
BUG("stat_tracking_info: invalid abf '%d'", abf);


/* Run "rev-list --left-right ours...theirs" internally... */ /* Run "rev-list --left-right ours...theirs" internally... */
argv_array_push(&argv, ""); /* ignored */ argv_array_push(&argv, ""); /* ignored */
Expand All @@ -2064,8 +2075,6 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
die("revision walk setup failed"); die("revision walk setup failed");


/* ... and count the commits on each side. */ /* ... and count the commits on each side. */
*num_ours = 0;
*num_theirs = 0;
while (1) { while (1) {
struct commit *c = get_revision(&revs); struct commit *c = get_revision(&revs);
if (!c) if (!c)
Expand All @@ -2081,20 +2090,22 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
clear_commit_marks(theirs, ALL_REV_FLAGS); clear_commit_marks(theirs, ALL_REV_FLAGS);


argv_array_clear(&argv); argv_array_clear(&argv);
return 0; return 1;
} }


/* /*
* Return true when there is anything to report, otherwise false. * Return true when there is anything to report, otherwise false.
*/ */
int format_tracking_info(struct branch *branch, struct strbuf *sb) int format_tracking_info(struct branch *branch, struct strbuf *sb,
enum ahead_behind_flags abf)
{ {
int ours, theirs; int ours, theirs, sti;
const char *full_base; const char *full_base;
char *base; char *base;
int upstream_is_gone = 0; int upstream_is_gone = 0;


if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) { sti = stat_tracking_info(branch, &ours, &theirs, &full_base, abf);
if (sti < 0) {
if (!full_base) if (!full_base)
return 0; return 0;
upstream_is_gone = 1; upstream_is_gone = 1;
Expand All @@ -2108,10 +2119,17 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
if (advice_status_hints) if (advice_status_hints)
strbuf_addstr(sb, strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n")); _(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!ours && !theirs) { } else if (!sti) {
strbuf_addf(sb, strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"), _("Your branch is up to date with '%s'.\n"),
base); base);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
base);
if (advice_status_hints)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) { } else if (!theirs) {
strbuf_addf(sb, strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n", Q_("Your branch is ahead of '%s' by %d commit.\n",
Expand Down
12 changes: 10 additions & 2 deletions remote.h
Expand Up @@ -257,10 +257,18 @@ enum match_refs_flags {
MATCH_REFS_FOLLOW_TAGS = (1 << 3) MATCH_REFS_FOLLOW_TAGS = (1 << 3)
}; };


/* Flags for --ahead-behind option. */
enum ahead_behind_flags {
AHEAD_BEHIND_UNSPECIFIED = -1,
AHEAD_BEHIND_QUICK = 0, /* just eq/neq reporting */
AHEAD_BEHIND_FULL = 1, /* traditional a/b reporting */
};

/* Reporting of tracking info */ /* Reporting of tracking info */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **upstream_name); const char **upstream_name, enum ahead_behind_flags abf);
int format_tracking_info(struct branch *branch, struct strbuf *sb); int format_tracking_info(struct branch *branch, struct strbuf *sb,
enum ahead_behind_flags abf);


struct ref *get_local_heads(void); struct ref *get_local_heads(void);
/* /*
Expand Down
42 changes: 42 additions & 0 deletions t/t6040-tracking-info.sh
Expand Up @@ -146,6 +146,48 @@ test_expect_success 'status -s -b (diverged from upstream)' '
test_i18ncmp expect actual test_i18ncmp expect actual
' '


cat >expect <<\EOF
## b1...origin/master [different]
EOF

test_expect_success 'status -s -b --no-ahead-behind (diverged from upstream)' '
(
cd test &&
git checkout b1 >/dev/null &&
git status -s -b --no-ahead-behind | head -1
) >actual &&
test_i18ncmp expect actual
'

cat >expect <<\EOF
On branch b1
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
EOF

test_expect_success 'status --long --branch' '
(
cd test &&
git checkout b1 >/dev/null &&
git status --long -b | head -3
) >actual &&
test_i18ncmp expect actual
'

cat >expect <<\EOF
On branch b1
Your branch and 'origin/master' refer to different commits.
EOF

test_expect_success 'status --long --branch --no-ahead-behind' '
(
cd test &&
git checkout b1 >/dev/null &&
git status --long -b --no-ahead-behind | head -2
) >actual &&
test_i18ncmp expect actual
'

cat >expect <<\EOF cat >expect <<\EOF
## b5...brokenbase [gone] ## b5...brokenbase [gone]
EOF EOF
Expand Down
62 changes: 62 additions & 0 deletions t/t7064-wtstatus-pv2.sh
Expand Up @@ -390,6 +390,68 @@ test_expect_success 'verify upstream fields in branch header' '
) )
' '


test_expect_success 'verify --[no-]ahead-behind with V2 format' '
git checkout master &&
test_when_finished "rm -rf sub_repo" &&
git clone . sub_repo &&
(
## Confirm local master tracks remote master.
cd sub_repo &&
HUF=$(git rev-parse HEAD) &&
# Confirm --no-ahead-behind reports traditional branch.ab with 0/0 for equal branches.
cat >expect <<-EOF &&
# branch.oid $HUF
# branch.head master
# branch.upstream origin/master
# branch.ab +0 -0
EOF
git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
test_cmp expect actual &&
# Confirm --ahead-behind reports traditional branch.ab with 0/0.
cat >expect <<-EOF &&
# branch.oid $HUF
# branch.head master
# branch.upstream origin/master
# branch.ab +0 -0
EOF
git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
test_cmp expect actual &&
## Test non-equal ahead/behind.
echo xyz >file_xyz &&
git add file_xyz &&
git commit -m xyz &&
HUF=$(git rev-parse HEAD) &&
# Confirm --no-ahead-behind reports branch.ab with ?/? for non-equal branches.
cat >expect <<-EOF &&
# branch.oid $HUF
# branch.head master
# branch.upstream origin/master
# branch.ab +? -?
EOF
git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
test_cmp expect actual &&
# Confirm --ahead-behind reports traditional branch.ab with 1/0.
cat >expect <<-EOF &&
# branch.oid $HUF
# branch.head master
# branch.upstream origin/master
# branch.ab +1 -0
EOF
git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
test_cmp expect actual
)
'

test_expect_success 'create and add submodule, submodule appears clean (A. S...)' ' test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
git checkout master && git checkout master &&
git clone . sub_repo && git clone . sub_repo &&
Expand Down

0 comments on commit f80bd10

Please sign in to comment.