Skip to content

Commit 46a16eb

Browse files
committed
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 <[email protected]>
2 parents 594c1c2 + 43ecd57 commit 46a16eb

10 files changed

+197
-35
lines changed

Documentation/git-status.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ ignored, then the directory is not shown, but all contents are shown.
137137
update it afterwards if any changes were detected. Defaults to
138138
`--lock-index`.
139139

140+
--ahead-behind::
141+
--no-ahead-behind::
142+
EXPERIMENTAL, Display or do not display detailed ahead/behind
143+
counts for the branch relative to its upstream branch. Defaults
144+
to true.
145+
140146
<pathspec>...::
141147
See the 'pathspec' entry in linkgit:gitglossary[7].
142148

builtin/checkout.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ static void report_tracking(struct branch_info *new)
612612
struct strbuf sb = STRBUF_INIT;
613613
struct branch *branch = branch_get(new->name);
614614

615-
if (!format_tracking_info(branch, &sb))
615+
if (!format_tracking_info(branch, &sb, AHEAD_BEHIND_FULL))
616616
return;
617617
fputs(sb.buf, stdout);
618618
strbuf_release(&sb);

builtin/commit.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,9 @@ static void finalize_deferred_config(struct wt_status *s)
11511151
s->show_branch = status_deferred_config.show_branch;
11521152
if (s->show_branch < 0)
11531153
s->show_branch = 0;
1154+
1155+
if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
1156+
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
11541157
}
11551158

11561159
static int parse_and_validate_options(int argc, const char *argv[],
@@ -1367,6 +1370,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
13671370
N_("show branch information")),
13681371
OPT_BOOL(0, "show-stash", &s.show_stash,
13691372
N_("show stash information")),
1373+
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
1374+
N_("compute full ahead/behind values (EXPERIMENTAL)")),
13701375
{ OPTION_CALLBACK, 0, "porcelain", &status_format,
13711376
N_("version"), N_("machine-readable output"),
13721377
PARSE_OPT_OPTARG, opt_parse_porcelain },
@@ -1670,6 +1675,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
16701675
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
16711676
STATUS_FORMAT_SHORT),
16721677
OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
1678+
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
1679+
N_("compute full ahead/behind values (EXPERIMENTAL)")),
16731680
OPT_SET_INT(0, "porcelain", &status_format,
16741681
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
16751682
OPT_SET_INT(0, "long", &status_format,

ref-filter.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,8 +1249,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
12491249
if (atom->u.remote_ref.option == RR_REF)
12501250
*s = show_ref(&atom->u.remote_ref.refname, refname);
12511251
else if (atom->u.remote_ref.option == RR_TRACK) {
1252-
if (stat_tracking_info(branch, &num_ours,
1253-
&num_theirs, NULL)) {
1252+
if (stat_tracking_info(branch, &num_ours, &num_theirs,
1253+
NULL, AHEAD_BEHIND_FULL) < 0) {
12541254
*s = xstrdup(msgs.gone);
12551255
} else if (!num_ours && !num_theirs)
12561256
*s = "";
@@ -1267,8 +1267,8 @@ static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
12671267
free((void *)to_free);
12681268
}
12691269
} else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
1270-
if (stat_tracking_info(branch, &num_ours,
1271-
&num_theirs, NULL))
1270+
if (stat_tracking_info(branch, &num_ours, &num_theirs,
1271+
NULL, AHEAD_BEHIND_FULL) < 0)
12721272
return;
12731273

12741274
if (!num_ours && !num_theirs)

remote.c

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2007,16 +2007,23 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
20072007
}
20082008

20092009
/*
2010-
* Compare a branch with its upstream, and save their differences (number
2011-
* of commits) in *num_ours and *num_theirs. The name of the upstream branch
2012-
* (or NULL if no upstream is defined) is returned via *upstream_name, if it
2013-
* is not itself NULL.
2010+
* Lookup the upstream branch for the given branch and if present, optionally
2011+
* compute the commit ahead/behind values for the pair.
2012+
*
2013+
* If abf is AHEAD_BEHIND_FULL, compute the full ahead/behind and return the
2014+
* counts in *num_ours and *num_theirs. If abf is AHEAD_BEHIND_QUICK, skip
2015+
* the (potentially expensive) a/b computation (*num_ours and *num_theirs are
2016+
* set to zero).
2017+
*
2018+
* The name of the upstream branch (or NULL if no upstream is defined) is
2019+
* returned via *upstream_name, if it is not itself NULL.
20142020
*
20152021
* Returns -1 if num_ours and num_theirs could not be filled in (e.g., no
2016-
* upstream defined, or ref does not exist), 0 otherwise.
2022+
* upstream defined, or ref does not exist). Returns 0 if the commits are
2023+
* identical. Returns 1 if commits are different.
20172024
*/
20182025
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
2019-
const char **upstream_name)
2026+
const char **upstream_name, enum ahead_behind_flags abf)
20202027
{
20212028
struct object_id oid;
20222029
struct commit *ours, *theirs;
@@ -2044,11 +2051,15 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
20442051
if (!ours)
20452052
return -1;
20462053

2054+
*num_theirs = *num_ours = 0;
2055+
20472056
/* are we the same? */
2048-
if (theirs == ours) {
2049-
*num_theirs = *num_ours = 0;
2057+
if (theirs == ours)
20502058
return 0;
2051-
}
2059+
if (abf == AHEAD_BEHIND_QUICK)
2060+
return 1;
2061+
if (abf != AHEAD_BEHIND_FULL)
2062+
BUG("stat_tracking_info: invalid abf '%d'", abf);
20522063

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

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

20832092
argv_array_clear(&argv);
2084-
return 0;
2093+
return 1;
20852094
}
20862095

20872096
/*
20882097
* Return true when there is anything to report, otherwise false.
20892098
*/
2090-
int format_tracking_info(struct branch *branch, struct strbuf *sb)
2099+
int format_tracking_info(struct branch *branch, struct strbuf *sb,
2100+
enum ahead_behind_flags abf)
20912101
{
2092-
int ours, theirs;
2102+
int ours, theirs, sti;
20932103
const char *full_base;
20942104
char *base;
20952105
int upstream_is_gone = 0;
20962106

2097-
if (stat_tracking_info(branch, &ours, &theirs, &full_base) < 0) {
2107+
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, abf);
2108+
if (sti < 0) {
20982109
if (!full_base)
20992110
return 0;
21002111
upstream_is_gone = 1;
@@ -2108,10 +2119,17 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
21082119
if (advice_status_hints)
21092120
strbuf_addstr(sb,
21102121
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2111-
} else if (!ours && !theirs) {
2122+
} else if (!sti) {
21122123
strbuf_addf(sb,
21132124
_("Your branch is up to date with '%s'.\n"),
21142125
base);
2126+
} else if (abf == AHEAD_BEHIND_QUICK) {
2127+
strbuf_addf(sb,
2128+
_("Your branch and '%s' refer to different commits.\n"),
2129+
base);
2130+
if (advice_status_hints)
2131+
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
2132+
"git status --ahead-behind");
21152133
} else if (!theirs) {
21162134
strbuf_addf(sb,
21172135
Q_("Your branch is ahead of '%s' by %d commit.\n",

remote.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,18 @@ enum match_refs_flags {
257257
MATCH_REFS_FOLLOW_TAGS = (1 << 3)
258258
};
259259

260+
/* Flags for --ahead-behind option. */
261+
enum ahead_behind_flags {
262+
AHEAD_BEHIND_UNSPECIFIED = -1,
263+
AHEAD_BEHIND_QUICK = 0, /* just eq/neq reporting */
264+
AHEAD_BEHIND_FULL = 1, /* traditional a/b reporting */
265+
};
266+
260267
/* Reporting of tracking info */
261268
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
262-
const char **upstream_name);
263-
int format_tracking_info(struct branch *branch, struct strbuf *sb);
269+
const char **upstream_name, enum ahead_behind_flags abf);
270+
int format_tracking_info(struct branch *branch, struct strbuf *sb,
271+
enum ahead_behind_flags abf);
264272

265273
struct ref *get_local_heads(void);
266274
/*

t/t6040-tracking-info.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,48 @@ test_expect_success 'status -s -b (diverged from upstream)' '
146146
test_i18ncmp expect actual
147147
'
148148

149+
cat >expect <<\EOF
150+
## b1...origin/master [different]
151+
EOF
152+
153+
test_expect_success 'status -s -b --no-ahead-behind (diverged from upstream)' '
154+
(
155+
cd test &&
156+
git checkout b1 >/dev/null &&
157+
git status -s -b --no-ahead-behind | head -1
158+
) >actual &&
159+
test_i18ncmp expect actual
160+
'
161+
162+
cat >expect <<\EOF
163+
On branch b1
164+
Your branch and 'origin/master' have diverged,
165+
and have 1 and 1 different commits each, respectively.
166+
EOF
167+
168+
test_expect_success 'status --long --branch' '
169+
(
170+
cd test &&
171+
git checkout b1 >/dev/null &&
172+
git status --long -b | head -3
173+
) >actual &&
174+
test_i18ncmp expect actual
175+
'
176+
177+
cat >expect <<\EOF
178+
On branch b1
179+
Your branch and 'origin/master' refer to different commits.
180+
EOF
181+
182+
test_expect_success 'status --long --branch --no-ahead-behind' '
183+
(
184+
cd test &&
185+
git checkout b1 >/dev/null &&
186+
git status --long -b --no-ahead-behind | head -2
187+
) >actual &&
188+
test_i18ncmp expect actual
189+
'
190+
149191
cat >expect <<\EOF
150192
## b5...brokenbase [gone]
151193
EOF

t/t7064-wtstatus-pv2.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,68 @@ test_expect_success 'verify upstream fields in branch header' '
390390
)
391391
'
392392

393+
test_expect_success 'verify --[no-]ahead-behind with V2 format' '
394+
git checkout master &&
395+
test_when_finished "rm -rf sub_repo" &&
396+
git clone . sub_repo &&
397+
(
398+
## Confirm local master tracks remote master.
399+
cd sub_repo &&
400+
HUF=$(git rev-parse HEAD) &&
401+
402+
# Confirm --no-ahead-behind reports traditional branch.ab with 0/0 for equal branches.
403+
cat >expect <<-EOF &&
404+
# branch.oid $HUF
405+
# branch.head master
406+
# branch.upstream origin/master
407+
# branch.ab +0 -0
408+
EOF
409+
410+
git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
411+
test_cmp expect actual &&
412+
413+
# Confirm --ahead-behind reports traditional branch.ab with 0/0.
414+
cat >expect <<-EOF &&
415+
# branch.oid $HUF
416+
# branch.head master
417+
# branch.upstream origin/master
418+
# branch.ab +0 -0
419+
EOF
420+
421+
git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
422+
test_cmp expect actual &&
423+
424+
## Test non-equal ahead/behind.
425+
echo xyz >file_xyz &&
426+
git add file_xyz &&
427+
git commit -m xyz &&
428+
429+
HUF=$(git rev-parse HEAD) &&
430+
431+
# Confirm --no-ahead-behind reports branch.ab with ?/? for non-equal branches.
432+
cat >expect <<-EOF &&
433+
# branch.oid $HUF
434+
# branch.head master
435+
# branch.upstream origin/master
436+
# branch.ab +? -?
437+
EOF
438+
439+
git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
440+
test_cmp expect actual &&
441+
442+
# Confirm --ahead-behind reports traditional branch.ab with 1/0.
443+
cat >expect <<-EOF &&
444+
# branch.oid $HUF
445+
# branch.head master
446+
# branch.upstream origin/master
447+
# branch.ab +1 -0
448+
EOF
449+
450+
git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
451+
test_cmp expect actual
452+
)
453+
'
454+
393455
test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
394456
git checkout master &&
395457
git clone . sub_repo &&

0 commit comments

Comments
 (0)