Skip to content

Commit 6a88f41

Browse files
status: add status.compareBranches config for multiple branch comparisons
Add a new configuration variable status.compareBranches that allows users to specify a space-separated list of branch comparisons in git status output. Supported values: - @{upstream} for the current branch's upstream tracking branch - @{push} for the current branch's push destination Any other value is ignored and a warning is shown. When not configured, the default behavior is equivalent to setting `status.compareBranches = @{upstream}`, preserving backward compatibility. The advice messages shown are context-aware: - "git pull" advice is shown only when comparing against @{upstream} - "git push" advice is shown only when comparing against @{push} - Divergence advice is shown for upstream branch comparisons This is useful for triangular workflows where the upstream tracking branch differs from the push destination, allowing users to see their status relative to both branches at once. Example configuration: [status] compareBranches = @{upstream} @{push} Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
1 parent 48db1f4 commit 6a88f41

File tree

3 files changed

+474
-28
lines changed

3 files changed

+474
-28
lines changed

Documentation/config/status.adoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ status.aheadBehind::
1717
`--no-ahead-behind` by default in linkgit:git-status[1] for
1818
non-porcelain status formats. Defaults to true.
1919

20+
status.compareBranches::
21+
A space-separated list of branch comparison specifiers to use in
22+
linkgit:git-status[1]. Currently, only `@{upstream}` and `@{push}`
23+
are supported. They are interpreted as `branch@{upstream}` and
24+
`branch@{push}` for the current branch.
25+
+
26+
If not set, the default behavior is equivalent to `@{upstream}`, which
27+
compares against the configured upstream tracking branch.
28+
+
29+
Example:
30+
+
31+
----
32+
[status]
33+
compareBranches = @{upstream} @{push}
34+
----
35+
+
36+
This would show comparisons against both the configured upstream and push
37+
tracking branches for the current branch.
38+
2039
status.displayCommentPrefix::
2140
If set to true, linkgit:git-status[1] will insert a comment
2241
prefix before each output line (starting with

remote.c

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929

3030
enum map_direction { FROM_SRC, FROM_DST };
3131

32+
enum {
33+
ENABLE_ADVICE_PULL = (1 << 0),
34+
ENABLE_ADVICE_PUSH = (1 << 1),
35+
ENABLE_ADVICE_DIVERGENCE = (1 << 2),
36+
};
37+
3238
struct counted_string {
3339
size_t len;
3440
const char *s;
@@ -2234,13 +2240,42 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
22342240
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
22352241
}
22362242

2243+
static char *resolve_compare_branch(struct branch *branch, const char *name)
2244+
{
2245+
const char *resolved = NULL;
2246+
2247+
if (!branch || !name)
2248+
return NULL;
2249+
2250+
if (!strcasecmp(name, "@{upstream}"))
2251+
resolved = branch_get_upstream(branch, NULL);
2252+
else if (!strcasecmp(name, "@{push}"))
2253+
resolved = branch_get_push(branch, NULL);
2254+
else {
2255+
warning(_("ignoring value '%s' for status.compareBranches; only @{upstream} and @{push} are supported"),
2256+
name);
2257+
return NULL;
2258+
}
2259+
2260+
if (resolved)
2261+
return xstrdup(resolved);
2262+
return NULL;
2263+
}
2264+
22372265
static void format_branch_comparison(struct strbuf *sb,
22382266
bool up_to_date,
22392267
int ours, int theirs,
22402268
const char *branch_name,
22412269
enum ahead_behind_flags abf,
2242-
bool show_divergence_advice)
2270+
unsigned flags)
22432271
{
2272+
bool enable_push_advice = (flags & ENABLE_ADVICE_PUSH) &&
2273+
advice_enabled(ADVICE_STATUS_HINTS);
2274+
bool enable_pull_advice = (flags & ENABLE_ADVICE_PULL) &&
2275+
advice_enabled(ADVICE_STATUS_HINTS);
2276+
bool enable_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE) &&
2277+
advice_enabled(ADVICE_STATUS_HINTS);
2278+
22442279
if (up_to_date) {
22452280
strbuf_addf(sb,
22462281
_("Your branch is up to date with '%s'.\n"),
@@ -2249,7 +2284,7 @@ static void format_branch_comparison(struct strbuf *sb,
22492284
strbuf_addf(sb,
22502285
_("Your branch and '%s' refer to different commits.\n"),
22512286
branch_name);
2252-
if (advice_enabled(ADVICE_STATUS_HINTS))
2287+
if (enable_push_advice)
22532288
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
22542289
"git status --ahead-behind");
22552290
} else if (!theirs) {
@@ -2258,7 +2293,7 @@ static void format_branch_comparison(struct strbuf *sb,
22582293
"Your branch is ahead of '%s' by %d commits.\n",
22592294
ours),
22602295
branch_name, ours);
2261-
if (advice_enabled(ADVICE_STATUS_HINTS))
2296+
if (enable_push_advice)
22622297
strbuf_addstr(sb,
22632298
_(" (use \"git push\" to publish your local commits)\n"));
22642299
} else if (!ours) {
@@ -2269,7 +2304,7 @@ static void format_branch_comparison(struct strbuf *sb,
22692304
"and can be fast-forwarded.\n",
22702305
theirs),
22712306
branch_name, theirs);
2272-
if (advice_enabled(ADVICE_STATUS_HINTS))
2307+
if (enable_pull_advice)
22732308
strbuf_addstr(sb,
22742309
_(" (use \"git pull\" to update your local branch)\n"));
22752310
} else {
@@ -2282,8 +2317,7 @@ static void format_branch_comparison(struct strbuf *sb,
22822317
"respectively.\n",
22832318
ours + theirs),
22842319
branch_name, ours, theirs);
2285-
if (show_divergence_advice &&
2286-
advice_enabled(ADVICE_STATUS_HINTS))
2320+
if (enable_divergence_advice)
22872321
strbuf_addstr(sb,
22882322
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
22892323
}
@@ -2296,34 +2330,92 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
22962330
enum ahead_behind_flags abf,
22972331
int show_divergence_advice)
22982332
{
2299-
int ours, theirs, cmp_fetch;
2300-
const char *full_base;
2301-
char *base;
2302-
int upstream_is_gone = 0;
2333+
char *compare_branches = NULL;
2334+
struct string_list branches = STRING_LIST_INIT_DUP;
2335+
struct strset processed_refs = STRSET_INIT;
2336+
int reported = 0;
2337+
size_t i;
2338+
const char *upstream_ref;
2339+
const char *push_ref;
23032340

2304-
cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
2305-
if (cmp_fetch < 0) {
2306-
if (!full_base)
2307-
return 0;
2308-
upstream_is_gone = 1;
2341+
repo_config_get_string(the_repository, "status.comparebranches",
2342+
&compare_branches);
2343+
2344+
if (compare_branches) {
2345+
string_list_split(&branches, compare_branches, " ", -1);
2346+
string_list_remove_empty_items(&branches, 0);
2347+
} else {
2348+
string_list_append(&branches, "@{upstream}");
23092349
}
23102350

2311-
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
2312-
full_base, 0);
2351+
upstream_ref = branch_get_upstream(branch, NULL);
2352+
push_ref = branch_get_push(branch, NULL);
23132353

2314-
if (upstream_is_gone) {
2315-
strbuf_addf(sb,
2316-
_("Your branch is based on '%s', but the upstream is gone.\n"),
2317-
base);
2318-
if (advice_enabled(ADVICE_STATUS_HINTS))
2319-
strbuf_addstr(sb,
2320-
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2321-
} else {
2322-
format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
2354+
for (i = 0; i < branches.nr; i++) {
2355+
char *full_ref;
2356+
char *short_ref;
2357+
int ours, theirs, cmp;
2358+
int is_upstream, is_push;
2359+
unsigned flags = 0;
2360+
2361+
full_ref = resolve_compare_branch(branch,
2362+
branches.items[i].string);
2363+
if (!full_ref)
2364+
continue;
2365+
2366+
if (!strset_add(&processed_refs, full_ref)) {
2367+
free(full_ref);
2368+
continue;
2369+
}
2370+
2371+
short_ref = refs_shorten_unambiguous_ref(
2372+
get_main_ref_store(the_repository), full_ref, 0);
2373+
2374+
is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref);
2375+
is_push = push_ref && !strcmp(full_ref, push_ref);
2376+
2377+
if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
2378+
is_push = 1;
2379+
2380+
cmp = stat_branch_pair(branch->refname, full_ref,
2381+
&ours, &theirs, abf);
2382+
2383+
if (cmp < 0) {
2384+
if (is_upstream) {
2385+
strbuf_addf(sb,
2386+
_("Your branch is based on '%s', but the upstream is gone.\n"),
2387+
short_ref);
2388+
if (advice_enabled(ADVICE_STATUS_HINTS))
2389+
strbuf_addstr(sb,
2390+
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2391+
reported = 1;
2392+
}
2393+
free(full_ref);
2394+
free(short_ref);
2395+
continue;
2396+
}
2397+
2398+
if (reported)
2399+
strbuf_addstr(sb, "\n");
2400+
2401+
if (is_upstream)
2402+
flags |= ENABLE_ADVICE_PULL;
2403+
if (is_push)
2404+
flags |= ENABLE_ADVICE_PUSH;
2405+
if (show_divergence_advice && is_upstream)
2406+
flags |= ENABLE_ADVICE_DIVERGENCE;
2407+
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
2408+
abf, flags);
2409+
reported = 1;
2410+
2411+
free(full_ref);
2412+
free(short_ref);
23232413
}
23242414

2325-
free(base);
2326-
return 1;
2415+
string_list_clear(&branches, 0);
2416+
strset_clear(&processed_refs);
2417+
free(compare_branches);
2418+
return reported;
23272419
}
23282420

23292421
static int one_local_ref(const struct reference *ref, void *cb_data)

0 commit comments

Comments
 (0)