Subject: [PATCH v3 0/3] wt-status: reduce reliance on global state

From: Shreyansh Paliwal <[email-masked]>
Date: Sat, 7 Feb 2026 15:30:45 +0530

In wt-status.c code still relies on some global variables, including
the_repository and the_hash_algo, even in cases where a repository
instance is already available via struct wt_status or struct worktree.

In patch 1/3, update function parameters and callers to pass struct
repository where no local repository access was available.

In patch 2/3, replace direct uses of the_repository with repository
instances already available in local structs.

In patch 3/3, replace remaining uses of the global the_hash_algo with the
hash algorithm stored in the respective repository instance.

These changes remove all direct uses of the_repository and
the_hash_algo from wt-status.c and reduce its dependence on global state.

The 'USE_THE_REPOSITORY_VARIABLE' macro cannot yet be removed, since these
patches only eliminate direct uses of the_repository and the_hash_algo,
while other global variables are still referenced.

In particular wt-status.c still relies on the following globals,

* core_apply_sparse_checkout, this is already being addressed in an
ongoing patch series [1].

* comment_line_str and DEFAULT_ABBREV, these both still are used in
wt-status.c but they dont have any equivalent local instances.

[1]- https://lore.kernel.org/git/[email-masked]/t/#u

Shreyansh Paliwal (3):
  wt-status: pass struct repository through function parameters
  wt-status: replace uses of the_repository with local repository instances
  wt-status: use hash_algo from local repository instead of global the_hash_algo

 branch.c    |   4 +--
 worktree.c  |   4 +--
 wt-status.c | 102 ++++++++++++++++++++++++++--------------------------
 wt-status.h |   6 ++--
 4 files changed, 60 insertions(+), 56 deletions(-)

---
Changes in v2:
 - Reordered the first 2 patches for better flow.
 - Instead of relying on wt->repo in wt_status_check_rebase() and
    wt_status_check_bisect() addded struct repository.
 - Added extra explainations in the commit message for the usage of wt
    and being it not NULL.

Range-diff against v2:
2:  2af0113f6f ! 1:  960216e45c wt-status: pass struct repository and wt_status through function parameters
    @@ Metadata
     Author: Shreyansh Paliwal <[email-masked]>
     
      ## Commit message ##
    -    wt-status: pass struct repository and wt_status through function parameters
    +    wt-status: pass struct repository through function parameters
     
    -    Some functions in wt-status.c relied on the_repository because no
    -    repository instance was available in their local scope.
    -    There is also a specific case in wt_status_check_rebase() where the
    -    worktree can be NULL, so accessing wt->repo may lead to a segfault.
    +    Some functions in wt-status.c (count_stash_entries(),
    +    read_line_from_git_path(), abbrev_oid_in_line(), read_rebase_todolist())
    +    do not have access to a local repository instance and rely on the_repository.
     
    -    Update these functions to accept a struct repository or struct
    -    wt_status parameter, and adjust callers accordingly. Replace the
    -    remaining uses of the_repository in these functions with the
    -    passed-in repository instance.
    +    Add a struct repository *r parameter to these functions, and pass the local
    +    repository through the callers.
     
    -    This removes the use of the_repository global variable from
    -    wt-status.c completely.
    +    get_branch(), wt_status_check_rebase() and wt_status_check_bisect() already
    +    receive a struct worktree *, which can provide access to the repository.
    +    However, some callers pass NULL as the worktree like in wt_status_get_state(),
    +    which would make using wt->repo unsafe and lead to segfault issues.
    +    Add an explicit struct repository * parameter to these functions as well,
    +    and pass the repository through the callers.
    +
    +    Both wt_status_check_rebase() and  wt_status_check_bisect() are called from
    +    branch.c and worktree.c,
    +
    +    * In branch.c, wt is always non-NULL as the functions are called within an
    +    interation over worktrees in prepare_checked_out_branches().
    +    * In worktree.c the functions are called from is_worktree_being_rebased() and
    +    is_worktree_being_bisected() respectively which are further called from
    +    builtin/branch.c in reject_rebase_or_bisect_branch() which has a non-NULL
    +    worktree as it is called inside an iteration over worktrees as well.
     
         Signed-off-by: Shreyansh Paliwal <[email-masked]>
     
    @@ branch.c: static void prepare_checked_out_branches(void)
      		    (state.rebase_in_progress || state.rebase_interactive_in_progress) &&
      		    state.branch) {
      			struct strbuf ref = STRBUF_INIT;
    +@@ branch.c: static void prepare_checked_out_branches(void)
    + 		}
    + 		wt_status_state_free_buffers(&state);
    + 
    +-		if (wt_status_check_bisect(wt, &state) &&
    ++		if (wt_status_check_bisect(wt->repo, wt, &state) &&
    + 		    state.bisecting_from) {
    + 			struct strbuf ref = STRBUF_INIT;
    + 			strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from);
     
      ## worktree.c ##
     @@ worktree.c: int is_worktree_being_rebased(const struct worktree *wt,
    @@ worktree.c: int is_worktree_being_rebased(const struct worktree *wt,
      		       (state.rebase_in_progress ||
      			state.rebase_interactive_in_progress) &&
      		       state.branch &&
    +@@ worktree.c: int is_worktree_being_bisected(const struct worktree *wt,
    + 	int found_bisect;
    + 
    + 	memset(&state, 0, sizeof(state));
    +-	found_bisect = wt_status_check_bisect(wt, &state) &&
    ++	found_bisect = wt_status_check_bisect(wt->repo, wt, &state) &&
    + 		       state.bisecting_from &&
    + 		       skip_prefix(target, "refs/heads/", &target) &&
    + 		       !strcmp(state.bisecting_from, target);
     
      ## wt-status.c ##
     @@ wt-status.c: static int stash_count_refs(const char *refname UNUSED,
    @@ wt-status.c: static int stash_count_refs(const char *refname UNUSED,
     +static int count_stash_entries(struct repository *r)
      {
      	int n = 0;
    --	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
    -+	refs_for_each_reflog_ent(get_main_ref_store(r),
    - 				 "refs/stash", stash_count_refs, &n);
    - 	return n;
    - }
    + 	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
    +@@ wt-status.c: static int count_stash_entries(void)
      
      static void wt_longstatus_print_stash_summary(struct wt_status *s)
      {
    @@ wt-status.c: static void show_am_in_progress(struct wt_status *s,
      }
      
     -static char *read_line_from_git_path(const char *filename)
    -+static char *read_line_from_git_path(struct repository *r, const char *filename)
    ++static char *read_line_from_git_path(struct repository *r, char *filename)
      {
      	struct strbuf buf = STRBUF_INIT;
    --	FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf,
    -+	FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf,
    - 						      "%s", filename), "r");
    - 
    - 	if (!fp) {
    + 	FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf,
     @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
      	if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF)
      		return 0;
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
      {
      	struct string_list split = STRING_LIST_INIT_DUP;
      	struct object_id oid;
    -@@ wt-status.c: static void abbrev_oid_in_line(struct strbuf *line)
    - 		return;
    - 
    - 	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
    --	    !repo_get_oid(the_repository, split.items[1].string, &oid)) {
    -+	    !repo_get_oid(r, split.items[1].string, &oid)) {
    - 		strbuf_reset(line);
    - 		strbuf_addf(line, "%s ", split.items[0].string);
    - 		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
     @@ wt-status.c: static void abbrev_oid_in_line(struct strbuf *line)
      	string_list_clear(&split, 0);
      }
      
     -static int read_rebase_todolist(const char *fname, struct string_list *lines)
    -+static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
    ++static int read_rebase_todolist(struct repository *r, char *fname, struct string_list *lines)
      {
      	struct strbuf buf = STRBUF_INIT;
    --	FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r");
    -+	FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r");
    - 	int ret;
    - 
    - 	if (!f) {
    -@@ wt-status.c: static int read_rebase_todolist(const char *fname, struct string_list *lines)
    - 			goto out;
    - 		}
    - 		die_errno("Could not open file %s for reading",
    --			  repo_git_path_replace(the_repository, &buf, "%s", fname));
    -+			  repo_git_path_replace(r, &buf, "%s", fname));
    - 	}
    - 	while (!strbuf_getline_lf(&buf, f)) {
    - 		if (starts_with(buf.buf, comment_line_str))
    + 	FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r");
     @@ wt-status.c: static int read_rebase_todolist(const char *fname, struct string_list *lines)
      		strbuf_trim(&buf);
      		if (!buf.len)
    @@ wt-status.c: static void show_rebase_information(struct wt_status *s,
      					 &yet_to_do))
      			status_printf_ln(s, color,
      				_("git-rebase-todo is missing."));
    +@@ wt-status.c: static void show_sparse_checkout_in_use(struct wt_status *s,
    + /*
    +  * Extract branch information from rebase/bisect
    +  */
    +-static char *get_branch(const struct worktree *wt, const char *path)
    ++static char *get_branch(struct repository *r, struct worktree *wt, const char *path)
    + {
    + 	struct strbuf sb = STRBUF_INIT;
    + 	struct object_id oid;
     @@ wt-status.c: static void wt_status_get_detached_from(struct repository *r,
      	strbuf_release(&cb.buf);
      }
    @@ wt-status.c: static void wt_status_get_detached_from(struct repository *r,
     -int wt_status_check_rebase(const struct worktree *wt,
     -			   struct wt_status_state *state)
     +int wt_status_check_rebase(struct repository *r,
    -+			 const struct worktree *wt,
    -+			 struct wt_status_state *state)
    ++	 			const struct worktree *wt,
    ++			    struct wt_status_state *state)
      {
      	struct stat st;
      
    --	if (!stat(worktree_git_path(the_repository, wt, "rebase-apply"), &st)) {
    --		if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/applying"), &st)) {
    -+	if (!stat(worktree_git_path(r, wt, "rebase-apply"), &st)) {
    -+		if (!stat(worktree_git_path(r, wt, "rebase-apply/applying"), &st)) {
    - 			state->am_in_progress = 1;
    --			if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/patch"), &st) && !st.st_size)
    -+			if (!stat(worktree_git_path(r, wt, "rebase-apply/patch"), &st) && !st.st_size)
    +@@ wt-status.c: int wt_status_check_rebase(const struct worktree *wt,
      				state->am_empty_patch = 1;
      		} else {
      			state->rebase_in_progress = 1;
    - 			state->branch = get_branch(wt, "rebase-apply/head-name");
    - 			state->onto = get_branch(wt, "rebase-apply/onto");
    +-			state->branch = get_branch(wt, "rebase-apply/head-name");
    +-			state->onto = get_branch(wt, "rebase-apply/onto");
    ++			state->branch = get_branch(r, wt, "rebase-apply/head-name");
    ++			state->onto = get_branch(r, wt, "rebase-apply/onto");
      		}
    --	} else if (!stat(worktree_git_path(the_repository, wt, "rebase-merge"), &st)) {
    --		if (!stat(worktree_git_path(the_repository, wt, "rebase-merge/interactive"), &st))
    -+	} else if (!stat(worktree_git_path(r, wt, "rebase-merge"), &st)) {
    -+		if (!stat(worktree_git_path(r, wt, "rebase-merge/interactive"), &st))
    + 	} else if (!stat(worktree_git_path(the_repository, wt, "rebase-merge"), &st)) {
    + 		if (!stat(worktree_git_path(the_repository, wt, "rebase-merge/interactive"), &st))
      			state->rebase_interactive_in_progress = 1;
      		else
      			state->rebase_in_progress = 1;
    +-		state->branch = get_branch(wt, "rebase-merge/head-name");
    +-		state->onto = get_branch(wt, "rebase-merge/onto");
    ++		state->branch = get_branch(r, wt, "rebase-merge/head-name");
    ++		state->onto = get_branch(r, wt, "rebase-merge/onto");
    + 	} else
    + 		return 0;
    + 	return 1;
    + }
    + 
    +-int wt_status_check_bisect(const struct worktree *wt,
    ++int wt_status_check_bisect(struct repository *r, 
    ++			   struct worktree *wt,
    + 			   struct wt_status_state *state)
    + {
    + 	struct stat st;
    + 
    + 	if (!stat(worktree_git_path(the_repository, wt, "BISECT_LOG"), &st)) {
    + 		state->bisect_in_progress = 1;
    +-		state->bisecting_from = get_branch(wt, "BISECT_START");
    ++		state->bisecting_from = get_branch(r, wt, "BISECT_START");
    + 		return 1;
    + 	}
    + 	return 0;
     @@ wt-status.c: void wt_status_get_state(struct repository *r,
      	enum replay_action action;
      
    @@ wt-status.c: void wt_status_get_state(struct repository *r,
      		;		/* all set */
      	} else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
      		   !repo_get_oid(r, "CHERRY_PICK_HEAD", &oid)) {
    + 		state->cherry_pick_in_progress = 1;
    + 		oidcpy(&state->cherry_pick_head_oid, &oid);
    + 	}
    +-	wt_status_check_bisect(NULL, state);
    ++	wt_status_check_bisect(r, NULL, state);
    + 	if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") &&
    + 	    !repo_get_oid(r, "REVERT_HEAD", &oid)) {
    + 		state->revert_in_progress = 1;
     @@ wt-status.c: static void wt_porcelain_v2_print_tracking(struct wt_status *s)
       */
      static void wt_porcelain_v2_print_stash(struct wt_status *s)
    @@ wt-status.h: void wt_status_state_free_buffers(struct wt_status_state *s);
      			 struct wt_status_state *state,
      			 int get_detached_from);
     -int wt_status_check_rebase(const struct worktree *wt,
    --			   struct wt_status_state *state);
     +int wt_status_check_rebase(struct repository *r,
    -+			 const struct worktree *wt,
    -+			 struct wt_status_state *state);
    - int wt_status_check_bisect(const struct worktree *wt,
    ++			   struct worktree *wt,
    + 			   struct wt_status_state *state);
    +-int wt_status_check_bisect(const struct worktree *wt,
    ++int wt_status_check_bisect(struct repository *r,
    ++			   struct worktree *wt,
      			   struct wt_status_state *state);
      
    + __attribute__((format (printf, 3, 4)))
1:  556735dfd4 ! 2:  906e682cd7 wt-status: replace uses of the_repository with local repository instances
    @@ Commit message
         wt-status: replace uses of the_repository with local repository instances
     
         wt-status.c uses the global the_repository in several places even when
    -    a repository instance is already available via struct wt_status or
    -    struct worktree.
    +    a repository instance is already available via struct wt_status *s or
    +    struct repository *r.
     
    -    Replace these direct uses of the_repository with the repository carried
    -    by the local structs (e.g. s->repo, wt->repo).
    +    Replace these uses of the_repository with the repository available
    +    in the local context (eg. s->repo or r).
     
         The replacements of all the_repository with s->repo are mostly
         to cases where a repository instance is already available via
    -    struct wt_status. All functions operating on struct wt_status *s
    -    are only used after s is initialized by wt_status_prepare(),
    +    struct wt_status *s and struct repository *r, all functions operating on
    +    struct wt_status *s are only used after s is initialized by wt_status_prepare(),
         which sets s->repo from the repository provided by the caller.
    -    As a result, s->repo is guaranteed to be available and consistent
    -    whenever these functions are invoked.
    +    As a result, s->repo is guaranteed to be available and consistent whenever
    +    these functions are invoked.
     
         This reduces reliance on global state and keeps wt-status consistent,
         though many functions operating on struct wt_status *s
    @@ wt-status.c: void wt_status_prepare(struct repository *r, struct wt_status *s)
      	s->reference = "HEAD";
      	s->fp = stdout;
     -	s->index_file = repo_get_index_file(the_repository);
    -+	s->index_file = repo_get_index_file(s->repo);
    ++	s->index_file = repo_get_index_file(r);
      	s->change.strdup_strings = 1;
      	s->untracked.strdup_strings = 1;
      	s->ignored.strdup_strings = 1;
    @@ wt-status.c: static void wt_status_collect_changes_index(struct wt_status *s)
      	setup_revisions(0, NULL, &rev, &opt);
      
      	rev.diffopt.flags.override_submodule_config = 1;
    +@@ wt-status.c: static int stash_count_refs(const char *refname UNUSED,
    + static int count_stash_entries(struct repository *r)
    + {
    + 	int n = 0;
    +-	refs_for_each_reflog_ent(get_main_ref_store(the_repository),
    ++	refs_for_each_reflog_ent(get_main_ref_store(r),
    + 				 "refs/stash", stash_count_refs, &n);
    + 	return n;
    + }
     @@ wt-status.c: static void wt_longstatus_print_verbose(struct wt_status *s)
      	rev.diffopt.ita_invisible_in_index = 1;
      
    @@ wt-status.c: static void wt_longstatus_print_verbose(struct wt_status *s)
      	setup_revisions(0, NULL, &rev, &opt);
      
      	rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
    +@@ wt-status.c: static void show_am_in_progress(struct wt_status *s,
    + static char *read_line_from_git_path(struct repository *r, char *filename)
    + {
    + 	struct strbuf buf = STRBUF_INIT;
    +-	FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf,
    ++	FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf,
    + 						      "%s", filename), "r");
    + 
    + 	if (!fp) {
     @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
      	    !s->branch || strcmp(s->branch, "HEAD"))
      		return 0;
    @@ wt-status.c: static int split_commit_in_progress(struct wt_status *s)
      			       &orig_head_oid, &orig_head_flags))
      		return 0;
      	if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF)
    +@@ wt-status.c: static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
    + 		return;
    + 
    + 	if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
    +-	    !repo_get_oid(the_repository, split.items[1].string, &oid)) {
    ++	    !repo_get_oid(r, split.items[1].string, &oid)) {
    + 		strbuf_reset(line);
    + 		strbuf_addf(line, "%s ", split.items[0].string);
    + 		strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
    +@@ wt-status.c: static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
    + static int read_rebase_todolist(struct repository *r, char *fname, struct string_list *lines)
    + {
    + 	struct strbuf buf = STRBUF_INIT;
    +-	FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r");
    ++	FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r");
    + 	int ret;
    + 
    + 	if (!f) {
    +@@ wt-status.c: static int read_rebase_todolist(struct repository *r, char *fname, struct string
    + 			goto out;
    + 		}
    + 		die_errno("Could not open file %s for reading",
    +-			  repo_git_path_replace(the_repository, &buf, "%s", fname));
    ++			  repo_git_path_replace(r, &buf, "%s", fname));
    + 	}
    + 	while (!strbuf_getline_lf(&buf, f)) {
    + 		if (starts_with(buf.buf, comment_line_str))
     @@ wt-status.c: static void show_rebase_information(struct wt_status *s,
      				i++)
      				status_printf_ln(s, color, "   %s", have_done.items[i].string);
    @@ wt-status.c: static void show_revert_in_progress(struct wt_status *s,
      						DEFAULT_ABBREV));
      	if (s->hints) {
      		if (has_unmerged(s))
    -@@ wt-status.c: static char *get_branch(const struct worktree *wt, const char *path)
    +@@ wt-status.c: static char *get_branch(struct repository *r, struct worktree *wt, const char *p
      	struct object_id oid;
      	const char *branch_name;
      
     -	if (strbuf_read_file(&sb, worktree_git_path(the_repository, wt, "%s", path), 0) <= 0)
    -+	if (strbuf_read_file(&sb, worktree_git_path(wt->repo, wt, "%s", path), 0) <= 0)
    ++	if (strbuf_read_file(&sb, worktree_git_path(r, wt, "%s", path), 0) <= 0)
      		goto got_nothing;
      
      	while (sb.len && sb.buf[sb.len - 1] == '\n')
    @@ wt-status.c: static void wt_status_get_detached_from(struct repository *r,
      		strbuf_release(&cb.buf);
      		return;
      	}
    -@@ wt-status.c: int wt_status_check_bisect(const struct worktree *wt,
    +@@ wt-status.c: int wt_status_check_rebase(struct repository *r,
    + {
    + 	struct stat st;
    + 
    +-	if (!stat(worktree_git_path(the_repository, wt, "rebase-apply"), &st)) {
    +-		if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/applying"), &st)) {
    ++	if (!stat(worktree_git_path(r, wt, "rebase-apply"), &st)) {
    ++		if (!stat(worktree_git_path(r, wt, "rebase-apply/applying"), &st)) {
    + 			state->am_in_progress = 1;
    +-			if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/patch"), &st) && !st.st_size)
    ++			if (!stat(worktree_git_path(r, wt, "rebase-apply/patch"), &st) && !st.st_size)
    + 				state->am_empty_patch = 1;
    + 		} else {
    + 			state->rebase_in_progress = 1;
    + 			state->branch = get_branch(r, wt, "rebase-apply/head-name");
    + 			state->onto = get_branch(r, wt, "rebase-apply/onto");
    + 		}
    +-	} else if (!stat(worktree_git_path(the_repository, wt, "rebase-merge"), &st)) {
    +-		if (!stat(worktree_git_path(the_repository, wt, "rebase-merge/interactive"), &st))
    ++	} else if (!stat(worktree_git_path(r, wt, "rebase-merge"), &st)) {
    ++		if (!stat(worktree_git_path(r, wt, "rebase-merge/interactive"), &st))
    + 			state->rebase_interactive_in_progress = 1;
    + 		else
    + 			state->rebase_in_progress = 1;
    +@@ wt-status.c: int wt_status_check_bisect(struct repository *r,
      {
      	struct stat st;
      
     -	if (!stat(worktree_git_path(the_repository, wt, "BISECT_LOG"), &st)) {
    -+	if (!stat(worktree_git_path(wt->repo, wt, "BISECT_LOG"), &st)) {
    ++	if (!stat(worktree_git_path(r, wt, "BISECT_LOG"), &st)) {
      		state->bisect_in_progress = 1;
    - 		state->bisecting_from = get_branch(wt, "BISECT_START");
    + 		state->bisecting_from = get_branch(r, wt, "BISECT_START");
      		return 1;
     @@ wt-status.c: static void wt_shortstatus_print_tracking(struct wt_status *s)
      		upstream_is_gone = 1;
3:  b745a08d96 = 3:  9c0a1d82ad wt-status: use hash_algo from local repository instead of global the_hash_algo
-- 
2.52.0