Skip to content
Permalink
Browse files

Merge branch 'no-ahead-behind-v5'

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 9, 2018
2 parents 01cd253 + 348108d commit f80bd10f48a67f447361762d90e4f3690b2c9009
Showing with 197 additions and 35 deletions.
  1. +6 −0 Documentation/git-status.txt
  2. +1 −1 builtin/checkout.c
  3. +7 −0 builtin/commit.c
  4. +4 −4 ref-filter.c
  5. +34 −16 remote.c
  6. +10 −2 remote.h
  7. +42 −0 t/t6040-tracking-info.sh
  8. +62 −0 t/t7064-wtstatus-pv2.sh
  9. +29 −12 wt-status.c
  10. +2 −0 wt-status.h
@@ -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
`--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>...::
See the 'pathspec' entry in linkgit:gitglossary[7].

@@ -607,7 +607,7 @@ static void report_tracking(struct branch_info *new)
struct strbuf sb = STRBUF_INIT;
struct branch *branch = branch_get(new->name);

if (!format_tracking_info(branch, &sb))
if (!format_tracking_info(branch, &sb, AHEAD_BEHIND_FULL))
return;
fputs(sb.buf, stdout);
strbuf_release(&sb);
@@ -1150,6 +1150,9 @@ static void finalize_deferred_config(struct wt_status *s)
s->show_branch = status_deferred_config.show_branch;
if (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[],
@@ -1366,6 +1369,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
N_("show branch information")),
OPT_BOOL(0, "show-stash", &s.show_stash,
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,
N_("version"), N_("machine-readable output"),
PARSE_OPT_OPTARG, opt_parse_porcelain },
@@ -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"),
STATUS_FORMAT_SHORT),
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,
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format,
@@ -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)
*s = show_ref(&atom->u.remote_ref.refname, refname);
else if (atom->u.remote_ref.option == RR_TRACK) {
if (stat_tracking_info(branch, &num_ours,
&num_theirs, NULL)) {
if (stat_tracking_info(branch, &num_ours, &num_theirs,
NULL, AHEAD_BEHIND_FULL) < 0) {
*s = xstrdup(msgs.gone);
} else if (!num_ours && !num_theirs)
*s = "";
@@ -1267,8 +1267,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
free((void *)to_free);
}
} else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
if (stat_tracking_info(branch, &num_ours,
&num_theirs, NULL))
if (stat_tracking_info(branch, &num_ours, &num_theirs,
NULL, AHEAD_BEHIND_FULL) < 0)
return;

if (!num_ours && !num_theirs)
@@ -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
* of commits) in *num_ours and *num_theirs. The name of the upstream branch
* (or NULL if no upstream is defined) is returned via *upstream_name, if it
* is not itself NULL.
* Lookup the upstream branch for the given branch and if present, optionally
* compute the commit ahead/behind values for the pair.
*
* 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
* 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,
const char **upstream_name)
const char **upstream_name, enum ahead_behind_flags abf)
{
struct object_id oid;
struct commit *ours, *theirs;
@@ -2044,11 +2051,15 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
if (!ours)
return -1;

*num_theirs = *num_ours = 0;

/* are we the same? */
if (theirs == ours) {
*num_theirs = *num_ours = 0;
if (theirs == ours)
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... */
argv_array_push(&argv, ""); /* ignored */
@@ -2064,8 +2075,6 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
die("revision walk setup failed");

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

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

/*
* 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;
char *base;
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)
return 0;
upstream_is_gone = 1;
@@ -2108,10 +2119,17 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
if (advice_status_hints)
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!ours && !theirs) {
} else if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
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) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
@@ -257,10 +257,18 @@ enum match_refs_flags {
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 */
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
const char **upstream_name);
int format_tracking_info(struct branch *branch, struct strbuf *sb);
const char **upstream_name, enum ahead_behind_flags abf);
int format_tracking_info(struct branch *branch, struct strbuf *sb,
enum ahead_behind_flags abf);

struct ref *get_local_heads(void);
/*
@@ -146,6 +146,48 @@ test_expect_success 'status -s -b (diverged from upstream)' '
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
## b5...brokenbase [gone]
EOF
@@ -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...)' '
git checkout master &&
git clone . sub_repo &&

0 comments on commit f80bd10

Please sign in to comment.
You can’t perform that action at this time.