Subject: [PATCH v27 0/2] status: add status.compareBranches config for multiple branch comparisons

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Thu, 22 Jan 2026 15:37:18 +0000

cc: Chris Torek [email-masked] cc: Yee Cheng Chin
[email-masked] cc: "brian m. carlson" [email-masked]
cc: Ben Knoble [email-masked] cc: "Kristoffer Haugsbakk"
[email-masked] cc: Phillip Wood [email-masked]
cc: Nico Williams [email-masked] cc: Patrick Steinhardt [email-masked] cc:
Jeff King [email-masked]

Harald Nordgren (2):
  refactor format_branch_comparison in preparation
  status: add status.compareBranches config for multiple branch
    comparisons

 Documentation/config/status.adoc |  20 ++
 remote.c                         | 192 ++++++++++++++----
 t/t6040-tracking-info.sh         | 337 +++++++++++++++++++++++++++++++
 3 files changed, 512 insertions(+), 37 deletions(-)

base-commit: b5c409c40f1595e3e590760c6f14a16b6683e22c
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v27
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v27
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v26:

 1:  27a46f8d9c = 1:  27a46f8d9c refactor format_branch_comparison in preparation
 2:  caa761f615 ! 2:  0993420fc1 status: add status.compareBranches config for multiple branch comparisons
     @@ remote.c
       struct counted_string {
       	size_t len;
       	const char *s;
     -@@ remote.c: static void branch_release(struct branch *branch)
     - 	free((char *)branch->refname);
     - 	free(branch->remote_name);
     - 	free(branch->pushremote_name);
     -+	free((char *)branch->push_tracking_ref);
     - 	merge_clear(branch);
     - }
     - 
      @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
       	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
       }
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      -	const char *full_base;
      -	char *base;
      -	int upstream_is_gone = 0;
     --
     ++	char *compare_branches = NULL;
     ++	struct string_list branches = STRING_LIST_INIT_DUP;
     ++	struct string_list processed_refs = STRING_LIST_INIT_DUP;
     ++	int reported = 0;
     ++	size_t i;
     ++	const char *upstream_ref;
     ++	const char *push_ref;
     + 
      -	cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
      -	if (cmp_fetch < 0) {
      -		if (!full_base)
      -			return 0;
      -		upstream_is_gone = 1;
     -+	char *compare_branches_config = NULL;
     -+	struct string_list compare_branches = STRING_LIST_INIT_DUP;
     -+	struct string_list_item *item;
     -+	int reported = 0;
     -+	size_t i;
     -+	const char *upstream_ref;
     -+	const char *push_ref;
     -+
      +	repo_config_get_string(the_repository, "status.comparebranches",
     -+			       &compare_branches_config);
     ++			       &compare_branches);
      +
     -+	if (compare_branches_config) {
     -+		string_list_split(&compare_branches, compare_branches_config,
     -+				  " ", -1);
     -+		string_list_remove_empty_items(&compare_branches, 0);
     ++	if (compare_branches) {
     ++		string_list_split(&branches, compare_branches, " ", -1);
     ++		string_list_remove_empty_items(&branches, 0);
      +	} else {
     -+		string_list_append(&compare_branches, "@{upstream}");
     ++		string_list_append(&branches, "@{upstream}");
       	}
       
      -	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      -				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
      -	} else {
      -		format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
     -+	for (i = 0; i < compare_branches.nr; i++) {
     ++	for (i = 0; i < branches.nr; i++) {
      +		char *full_ref;
      +		char *short_ref;
      +		int ours, theirs, cmp;
      +		int is_upstream, is_push;
      +		unsigned flags = 0;
      +
     -+		item = &compare_branches.items[i];
     -+		full_ref = resolve_compare_branch(branch, item->string);
     ++		full_ref = resolve_compare_branch(branch,
     ++						  branches.items[i].string);
      +		if (!full_ref)
      +			continue;
      +
     ++		if (string_list_has_string(&processed_refs, full_ref)) {
     ++			free(full_ref);
     ++			continue;
     ++		}
     ++		string_list_insert(&processed_refs, full_ref);
     ++
      +		short_ref = refs_shorten_unambiguous_ref(
      +			get_main_ref_store(the_repository), full_ref, 0);
      +
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       
      -	free(base);
      -	return 1;
     -+	string_list_clear(&compare_branches, 0);
     -+	free(compare_branches_config);
     ++	string_list_clear(&branches, 0);
     ++	string_list_clear(&processed_refs, 0);
     ++	free(compare_branches);
      +	return reported;
       }
       
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	)
      +'
      +
     ++test_expect_success 'status.compareBranches from upstream has no duplicates' '
     ++	(
     ++		cd test &&
     ++		git checkout main &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch main
     ++	Your branch is up to date with ${SQ}origin/main${SQ}.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
      +test_expect_success 'status.compareBranches shows ahead of both upstream and push branch' '
      +	(
      +		cd test &&

-- 
gitgitgadget