Skip to content

Commit 3ea95ac

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 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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 04f4726 commit 3ea95ac

3 files changed

Lines changed: 447 additions & 28 deletions

File tree

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: 118 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;
@@ -2241,13 +2247,40 @@ 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+
const char *resolved = NULL;
2253+
2254+
if (!branch || !name)
2255+
return NULL;
2256+
2257+
if (!strcasecmp(name, "@{upstream}")) {
2258+
resolved = branch_get_upstream(branch, NULL);
2259+
} else if (!strcasecmp(name, "@{push}")) {
2260+
resolved = branch_get_push(branch, NULL);
2261+
} else {
2262+
warning(_("ignoring value '%s' for status.compareBranches, "
2263+
"only @{upstream} and @{push} are supported"),
2264+
name);
2265+
return NULL;
2266+
}
2267+
2268+
if (resolved)
2269+
return xstrdup(resolved);
2270+
return NULL;
2271+
}
2272+
22442273
static void format_branch_comparison(struct strbuf *sb,
22452274
bool up_to_date,
22462275
int ours, int theirs,
22472276
const char *branch_name,
22482277
enum ahead_behind_flags abf,
2249-
bool show_divergence_advice)
2278+
unsigned flags)
22502279
{
2280+
bool use_push_advice = (flags & ENABLE_ADVICE_PUSH);
2281+
bool use_pull_advice = (flags & ENABLE_ADVICE_PULL);
2282+
bool use_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE);
2283+
22512284
if (up_to_date) {
22522285
strbuf_addf(sb,
22532286
_("Your branch is up to date with '%s'.\n"),
@@ -2256,7 +2289,7 @@ static void format_branch_comparison(struct strbuf *sb,
22562289
strbuf_addf(sb,
22572290
_("Your branch and '%s' refer to different commits.\n"),
22582291
branch_name);
2259-
if (advice_enabled(ADVICE_STATUS_HINTS))
2292+
if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
22602293
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
22612294
"git status --ahead-behind");
22622295
} else if (!theirs) {
@@ -2265,7 +2298,7 @@ static void format_branch_comparison(struct strbuf *sb,
22652298
"Your branch is ahead of '%s' by %d commits.\n",
22662299
ours),
22672300
branch_name, ours);
2268-
if (advice_enabled(ADVICE_STATUS_HINTS))
2301+
if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
22692302
strbuf_addstr(sb,
22702303
_(" (use \"git push\" to publish your local commits)\n"));
22712304
} else if (!ours) {
@@ -2276,7 +2309,7 @@ static void format_branch_comparison(struct strbuf *sb,
22762309
"and can be fast-forwarded.\n",
22772310
theirs),
22782311
branch_name, theirs);
2279-
if (advice_enabled(ADVICE_STATUS_HINTS))
2312+
if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
22802313
strbuf_addstr(sb,
22812314
_(" (use \"git pull\" to update your local branch)\n"));
22822315
} else {
@@ -2289,8 +2322,7 @@ static void format_branch_comparison(struct strbuf *sb,
22892322
"respectively.\n",
22902323
ours + theirs),
22912324
branch_name, ours, theirs);
2292-
if (show_divergence_advice &&
2293-
advice_enabled(ADVICE_STATUS_HINTS))
2325+
if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
22942326
strbuf_addstr(sb,
22952327
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
22962328
}
@@ -2303,34 +2335,92 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
23032335
enum ahead_behind_flags abf,
23042336
int show_divergence_advice)
23052337
{
2306-
int ours, theirs, cmp_fetch;
2307-
const char *full_base;
2308-
char *base;
2309-
int upstream_is_gone = 0;
2338+
char *compare_branches = NULL;
2339+
struct string_list branches = STRING_LIST_INIT_DUP;
2340+
struct strset processed_refs = STRSET_INIT;
2341+
int reported = 0;
2342+
size_t i;
2343+
const char *upstream_ref;
2344+
const char *push_ref;
23102345

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;
2346+
repo_config_get_string(the_repository, "status.comparebranches",
2347+
&compare_branches);
2348+
2349+
if (compare_branches) {
2350+
string_list_split(&branches, compare_branches, " ", -1);
2351+
string_list_remove_empty_items(&branches, 0);
2352+
} else {
2353+
string_list_append(&branches, "@{upstream}");
23162354
}
23172355

2318-
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
2319-
full_base, 0);
2356+
upstream_ref = branch_get_upstream(branch, NULL);
2357+
push_ref = branch_get_push(branch, NULL);
23202358

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

2332-
free(base);
2333-
return 1;
2420+
string_list_clear(&branches, 0);
2421+
strset_clear(&processed_refs);
2422+
free(compare_branches);
2423+
return reported;
23342424
}
23352425

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

0 commit comments

Comments
 (0)