Skip to content

Commit 6fd2937

Browse files
HaraldNordgrengitster
authored andcommitted
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 branches to compare against the current branch in `git status` output. Each branch in the list can be: - A remote-tracking branch name (e.g., `origin/main`) - The special reference `@{upstream}` for the tracking branch - The special reference `@{push}` for the push destination 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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 1c1b38b commit 6fd2937

File tree

3 files changed

+469
-29
lines changed

3 files changed

+469
-29
lines changed

Documentation/config/status.adoc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ 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 branches to compare the current branch
22+
against in linkgit:git-status[1]. Each branch specification can be
23+
a remote-tracking branch name (e.g. `origin/main`), or a special
24+
reference like `@{upstream}` or `@{push}`. For each branch in the
25+
list, git status shows whether the current branch is ahead, behind,
26+
or has diverged from that branch.
27+
+
28+
If not set, the default behavior is equivalent to `@{upstream}`, which
29+
compares against the configured upstream tracking branch.
30+
+
31+
Example:
32+
+
33+
----
34+
[status]
35+
compareBranches = origin/main origin/develop
36+
----
37+
+
38+
This would show comparisons against both `origin/main` and `origin/develop`.
39+
2040
status.displayCommentPrefix::
2141
If set to true, linkgit:git-status[1] will insert a comment
2242
prefix before each output line (starting with

remote.c

Lines changed: 127 additions & 29 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;
@@ -2241,13 +2247,53 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
22412247
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
22422248
}
22432249

2250+
static char *resolve_compare_branch(struct branch *branch, const char *name)
2251+
{
2252+
struct strbuf buf = STRBUF_INIT;
2253+
const char *resolved = NULL;
2254+
char *ret;
2255+
2256+
if (!branch || !name)
2257+
return NULL;
2258+
2259+
if (!strcasecmp(name, "@{upstream}") || !strcasecmp(name, "@{u}"))
2260+
resolved = branch_get_upstream(branch, NULL);
2261+
else if (!strcasecmp(name, "@{push}"))
2262+
resolved = branch_get_push(branch, NULL);
2263+
2264+
if (resolved)
2265+
return xstrdup(resolved);
2266+
2267+
strbuf_addf(&buf, "refs/remotes/%s", name);
2268+
resolved = refs_resolve_ref_unsafe(
2269+
get_main_ref_store(the_repository),
2270+
buf.buf,
2271+
RESOLVE_REF_READING,
2272+
NULL, NULL);
2273+
if (resolved) {
2274+
ret = xstrdup(resolved);
2275+
strbuf_release(&buf);
2276+
return ret;
2277+
}
2278+
2279+
strbuf_release(&buf);
2280+
return NULL;
2281+
}
2282+
22442283
static void format_branch_comparison(struct strbuf *sb,
22452284
bool up_to_date,
22462285
int ours, int theirs,
22472286
const char *branch_name,
22482287
enum ahead_behind_flags abf,
2249-
bool show_divergence_advice)
2288+
unsigned flags)
22502289
{
2290+
bool enable_push_advice = (flags & ENABLE_ADVICE_PUSH) &&
2291+
advice_enabled(ADVICE_STATUS_HINTS);
2292+
bool enable_pull_advice = (flags & ENABLE_ADVICE_PULL) &&
2293+
advice_enabled(ADVICE_STATUS_HINTS);
2294+
bool enable_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE) &&
2295+
advice_enabled(ADVICE_STATUS_HINTS);
2296+
22512297
if (up_to_date) {
22522298
strbuf_addf(sb,
22532299
_("Your branch is up to date with '%s'.\n"),
@@ -2256,7 +2302,7 @@ static void format_branch_comparison(struct strbuf *sb,
22562302
strbuf_addf(sb,
22572303
_("Your branch and '%s' refer to different commits.\n"),
22582304
branch_name);
2259-
if (advice_enabled(ADVICE_STATUS_HINTS))
2305+
if (enable_push_advice)
22602306
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
22612307
"git status --ahead-behind");
22622308
} else if (!theirs) {
@@ -2265,7 +2311,7 @@ static void format_branch_comparison(struct strbuf *sb,
22652311
"Your branch is ahead of '%s' by %d commits.\n",
22662312
ours),
22672313
branch_name, ours);
2268-
if (advice_enabled(ADVICE_STATUS_HINTS))
2314+
if (enable_push_advice)
22692315
strbuf_addstr(sb,
22702316
_(" (use \"git push\" to publish your local commits)\n"));
22712317
} else if (!ours) {
@@ -2276,7 +2322,7 @@ static void format_branch_comparison(struct strbuf *sb,
22762322
"and can be fast-forwarded.\n",
22772323
theirs),
22782324
branch_name, theirs);
2279-
if (advice_enabled(ADVICE_STATUS_HINTS))
2325+
if (enable_pull_advice)
22802326
strbuf_addstr(sb,
22812327
_(" (use \"git pull\" to update your local branch)\n"));
22822328
} else {
@@ -2289,8 +2335,7 @@ static void format_branch_comparison(struct strbuf *sb,
22892335
"respectively.\n",
22902336
ours + theirs),
22912337
branch_name, ours, theirs);
2292-
if (show_divergence_advice &&
2293-
advice_enabled(ADVICE_STATUS_HINTS))
2338+
if (enable_divergence_advice)
22942339
strbuf_addstr(sb,
22952340
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
22962341
}
@@ -2303,34 +2348,87 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
23032348
enum ahead_behind_flags abf,
23042349
int show_divergence_advice)
23052350
{
2306-
int ours, theirs, cmp_fetch;
2307-
const char *full_base;
2308-
char *base;
2309-
int upstream_is_gone = 0;
2310-
2311-
cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
2312-
if (cmp_fetch < 0) {
2313-
if (!full_base)
2314-
return 0;
2315-
upstream_is_gone = 1;
2351+
char *compare_branches_config = NULL;
2352+
struct string_list compare_branches = STRING_LIST_INIT_DUP;
2353+
struct string_list_item *item;
2354+
int reported = 0;
2355+
size_t i;
2356+
const char *upstream_ref;
2357+
const char *push_ref;
2358+
2359+
repo_config_get_string(the_repository, "status.comparebranches",
2360+
&compare_branches_config);
2361+
2362+
if (compare_branches_config) {
2363+
string_list_split(&compare_branches, compare_branches_config,
2364+
" ", -1);
2365+
string_list_remove_empty_items(&compare_branches, 0);
2366+
} else {
2367+
string_list_append(&compare_branches, "@{upstream}");
23162368
}
23172369

2318-
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
2319-
full_base, 0);
2370+
upstream_ref = branch_get_upstream(branch, NULL);
2371+
push_ref = branch_get_push(branch, NULL);
23202372

2321-
if (upstream_is_gone) {
2322-
strbuf_addf(sb,
2323-
_("Your branch is based on '%s', but the upstream is gone.\n"),
2324-
base);
2325-
if (advice_enabled(ADVICE_STATUS_HINTS))
2326-
strbuf_addstr(sb,
2327-
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2328-
} else {
2329-
format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
2373+
for (i = 0; i < compare_branches.nr; i++) {
2374+
char *full_ref;
2375+
char *short_ref;
2376+
int ours, theirs, cmp;
2377+
int is_upstream, is_push;
2378+
unsigned flags = 0;
2379+
2380+
item = &compare_branches.items[i];
2381+
full_ref = resolve_compare_branch(branch, item->string);
2382+
if (!full_ref)
2383+
continue;
2384+
2385+
short_ref = refs_shorten_unambiguous_ref(
2386+
get_main_ref_store(the_repository), full_ref, 0);
2387+
2388+
is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref);
2389+
is_push = push_ref && !strcmp(full_ref, push_ref);
2390+
2391+
if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
2392+
is_push = 1;
2393+
2394+
cmp = stat_branch_pair(branch->refname, full_ref,
2395+
&ours, &theirs, abf);
2396+
2397+
if (cmp < 0) {
2398+
if (is_upstream) {
2399+
strbuf_addf(sb,
2400+
_("Your branch is based on '%s', but the upstream is gone.\n"),
2401+
short_ref);
2402+
if (advice_enabled(ADVICE_STATUS_HINTS))
2403+
strbuf_addstr(sb,
2404+
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2405+
reported = 1;
2406+
}
2407+
free(full_ref);
2408+
free(short_ref);
2409+
continue;
2410+
}
2411+
2412+
if (reported)
2413+
strbuf_addstr(sb, "\n");
2414+
2415+
if (is_upstream)
2416+
flags |= ENABLE_ADVICE_PULL;
2417+
if (is_push)
2418+
flags |= ENABLE_ADVICE_PUSH;
2419+
if (show_divergence_advice && is_upstream)
2420+
flags |= ENABLE_ADVICE_DIVERGENCE;
2421+
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
2422+
abf, flags);
2423+
reported = 1;
2424+
2425+
free(full_ref);
2426+
free(short_ref);
23302427
}
23312428

2332-
free(base);
2333-
return 1;
2429+
string_list_clear(&compare_branches, 0);
2430+
free(compare_branches_config);
2431+
return reported;
23342432
}
23352433

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

0 commit comments

Comments
 (0)