Skip to content

Commit d0413b3

Browse files
committed
Merge branch 'hn/status-compare-with-push'
"git status" learned to show comparison between the current branch and various other branches listed on status.compareBranches configuration. * hn/status-compare-with-push: status: clarify how status.compareBranches deduplicates status: add status.compareBranches config for multiple branch comparisons refactor format_branch_comparison in preparation
2 parents 0316174 + 68791d7 commit d0413b3

3 files changed

Lines changed: 520 additions & 37 deletions

File tree

Documentation/config/status.adoc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@ 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+
The entries are shown in the order they appear in the configuration.
30+
Duplicate entries that resolve to the same ref are suppressed after
31+
their first occurrence, so `@{push} @{upstream} @{push}` shows at
32+
most two comparisons. When `@{upstream}` and `@{push}` resolve to
33+
the same remote-tracking branch, only one comparison is shown.
34+
+
35+
Example:
36+
+
37+
----
38+
[status]
39+
compareBranches = @{upstream} @{push}
40+
----
41+
+
42+
This would show comparisons against both the configured upstream and push
43+
tracking branches for the current branch.
44+
2045
status.displayCommentPrefix::
2146
If set to true, linkgit:git-status[1] will insert a comment
2247
prefix before each output line (starting with

remote.c

Lines changed: 141 additions & 37 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,52 +2240,58 @@ 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

2237-
/*
2238-
* Return true when there is anything to report, otherwise false.
2239-
*/
2240-
int format_tracking_info(struct branch *branch, struct strbuf *sb,
2241-
enum ahead_behind_flags abf,
2242-
int show_divergence_advice)
2243+
static char *resolve_compare_branch(struct branch *branch, const char *name)
22432244
{
2244-
int ours, theirs, sti;
2245-
const char *full_base;
2246-
char *base;
2247-
int upstream_is_gone = 0;
2245+
const char *resolved = NULL;
22482246

2249-
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
2250-
if (sti < 0) {
2251-
if (!full_base)
2252-
return 0;
2253-
upstream_is_gone = 1;
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, "
2256+
"only @{upstream} and @{push} are supported"),
2257+
name);
2258+
return NULL;
22542259
}
22552260

2256-
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
2257-
full_base, 0);
2258-
if (upstream_is_gone) {
2259-
strbuf_addf(sb,
2260-
_("Your branch is based on '%s', but the upstream is gone.\n"),
2261-
base);
2262-
if (advice_enabled(ADVICE_STATUS_HINTS))
2263-
strbuf_addstr(sb,
2264-
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2265-
} else if (!sti) {
2261+
if (resolved)
2262+
return xstrdup(resolved);
2263+
return NULL;
2264+
}
2265+
2266+
static void format_branch_comparison(struct strbuf *sb,
2267+
bool up_to_date,
2268+
int ours, int theirs,
2269+
const char *branch_name,
2270+
enum ahead_behind_flags abf,
2271+
unsigned flags)
2272+
{
2273+
bool use_push_advice = (flags & ENABLE_ADVICE_PUSH);
2274+
bool use_pull_advice = (flags & ENABLE_ADVICE_PULL);
2275+
bool use_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE);
2276+
2277+
if (up_to_date) {
22662278
strbuf_addf(sb,
22672279
_("Your branch is up to date with '%s'.\n"),
2268-
base);
2280+
branch_name);
22692281
} else if (abf == AHEAD_BEHIND_QUICK) {
22702282
strbuf_addf(sb,
22712283
_("Your branch and '%s' refer to different commits.\n"),
2272-
base);
2273-
if (advice_enabled(ADVICE_STATUS_HINTS))
2284+
branch_name);
2285+
if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
22742286
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
22752287
"git status --ahead-behind");
22762288
} else if (!theirs) {
22772289
strbuf_addf(sb,
22782290
Q_("Your branch is ahead of '%s' by %d commit.\n",
22792291
"Your branch is ahead of '%s' by %d commits.\n",
22802292
ours),
2281-
base, ours);
2282-
if (advice_enabled(ADVICE_STATUS_HINTS))
2293+
branch_name, ours);
2294+
if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS))
22832295
strbuf_addstr(sb,
22842296
_(" (use \"git push\" to publish your local commits)\n"));
22852297
} else if (!ours) {
@@ -2289,8 +2301,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
22892301
"Your branch is behind '%s' by %d commits, "
22902302
"and can be fast-forwarded.\n",
22912303
theirs),
2292-
base, theirs);
2293-
if (advice_enabled(ADVICE_STATUS_HINTS))
2304+
branch_name, theirs);
2305+
if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
22942306
strbuf_addstr(sb,
22952307
_(" (use \"git pull\" to update your local branch)\n"));
22962308
} else {
@@ -2302,14 +2314,106 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
23022314
"and have %d and %d different commits each, "
23032315
"respectively.\n",
23042316
ours + theirs),
2305-
base, ours, theirs);
2306-
if (show_divergence_advice &&
2307-
advice_enabled(ADVICE_STATUS_HINTS))
2317+
branch_name, ours, theirs);
2318+
if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
23082319
strbuf_addstr(sb,
23092320
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
23102321
}
2311-
free(base);
2312-
return 1;
2322+
}
2323+
2324+
/*
2325+
* Return true when there is anything to report, otherwise false.
2326+
*/
2327+
int format_tracking_info(struct branch *branch, struct strbuf *sb,
2328+
enum ahead_behind_flags abf,
2329+
int show_divergence_advice)
2330+
{
2331+
char *compare_branches = NULL;
2332+
struct string_list branches = STRING_LIST_INIT_DUP;
2333+
struct strset processed_refs = STRSET_INIT;
2334+
int reported = 0;
2335+
size_t i;
2336+
const char *upstream_ref;
2337+
const char *push_ref;
2338+
2339+
repo_config_get_string(the_repository, "status.comparebranches",
2340+
&compare_branches);
2341+
2342+
if (compare_branches) {
2343+
string_list_split(&branches, compare_branches, " ", -1);
2344+
string_list_remove_empty_items(&branches, 0);
2345+
} else {
2346+
string_list_append(&branches, "@{upstream}");
2347+
}
2348+
2349+
upstream_ref = branch_get_upstream(branch, NULL);
2350+
push_ref = branch_get_push(branch, NULL);
2351+
2352+
for (i = 0; i < branches.nr; i++) {
2353+
char *full_ref;
2354+
char *short_ref;
2355+
int ours, theirs, cmp;
2356+
int is_upstream, is_push;
2357+
unsigned flags = 0;
2358+
2359+
full_ref = resolve_compare_branch(branch,
2360+
branches.items[i].string);
2361+
if (!full_ref)
2362+
continue;
2363+
2364+
if (!strset_add(&processed_refs, full_ref)) {
2365+
free(full_ref);
2366+
continue;
2367+
}
2368+
2369+
short_ref = refs_shorten_unambiguous_ref(
2370+
get_main_ref_store(the_repository), full_ref, 0);
2371+
2372+
is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref);
2373+
is_push = push_ref && !strcmp(full_ref, push_ref);
2374+
2375+
if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref)))
2376+
is_push = 1;
2377+
2378+
cmp = stat_branch_pair(branch->refname, full_ref,
2379+
&ours, &theirs, abf);
2380+
2381+
if (cmp < 0) {
2382+
if (is_upstream) {
2383+
strbuf_addf(sb,
2384+
_("Your branch is based on '%s', but the upstream is gone.\n"),
2385+
short_ref);
2386+
if (advice_enabled(ADVICE_STATUS_HINTS))
2387+
strbuf_addstr(sb,
2388+
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
2389+
reported = 1;
2390+
}
2391+
free(full_ref);
2392+
free(short_ref);
2393+
continue;
2394+
}
2395+
2396+
if (reported)
2397+
strbuf_addstr(sb, "\n");
2398+
2399+
if (is_upstream)
2400+
flags |= ENABLE_ADVICE_PULL;
2401+
if (is_push)
2402+
flags |= ENABLE_ADVICE_PUSH;
2403+
if (show_divergence_advice && is_upstream)
2404+
flags |= ENABLE_ADVICE_DIVERGENCE;
2405+
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
2406+
abf, flags);
2407+
reported = 1;
2408+
2409+
free(full_ref);
2410+
free(short_ref);
2411+
}
2412+
2413+
string_list_clear(&branches, 0);
2414+
strset_clear(&processed_refs);
2415+
free(compare_branches);
2416+
return reported;
23132417
}
23142418

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

0 commit comments

Comments
 (0)