Subject: [PATCH v6 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early

From: Shroom Moo <[email-masked]>
Date: Thu, 7 May 2026 04:27:51 +0800

Users expect these two invocations to be equivalent:

    GIT_WORK_TREE=/some/path GIT_DIR=/some/path/.git git gui
    git -C /some/path gui

Currently, the environment variable variant often brings up the
repository picker or ignores the requested worktree because
GIT_WORK_TREE is processed too late.  Moreover, after determining
the working tree, git-gui unconditionally exports GIT_WORK_TREE.
When no worktree is found (e.g., in a bare repository with a
read-only subcommand like blame), an empty value is exported, which
confuses commands like `git branch --show-current`.

Fix both issues:

- Introduce resolve_initial_environment, which is called before any
  repository detection.  When both GIT_DIR and GIT_WORK_TREE are
  set, it changes to the specified worktree, verifies that it is a
  valid worktree, and adopts the given GIT_DIR.  In this case, the
  subsequent automatic detection is skipped.  After successful
  validation, GIT_WORK_TREE is unset so that later steps do not
  inherit it.

- At the end of startup, only export GIT_DIR.  The current directory
  is already at the top of the worktree (when a worktree exists), so
  Git can discover the worktree automatically.  Not exporting an
  empty GIT_WORK_TREE fixes `git gui blame` in bare repositories.

When only GIT_DIR is set without GIT_WORK_TREE, the existing logic
remains unchanged for backward compatibility.  Setting only
GIT_WORK_TREE is not a valid Git use-case and is silently ignored.

Helped-by: Mark Levedahl <[email-masked]>
Helped-by: Johannes Sixt <[email-masked]>
Signed-off-by: Shroom Moo <[email-masked]>
---
 git-gui/git-gui.sh | 84 ++++++++++++++++++++++++++++++++++++----------
 1 file changed, 66 insertions(+), 18 deletions(-)

diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 1191e6654c..ca651dc576 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1122,25 +1122,69 @@ unset argv0dir
 ##
 ## repository setup
 
+proc resolve_initial_environment {} {
+	global _gitdir env
+
+	# Only act if both GIT_DIR and GIT_WORK_TREE are set
+	if {[info exists env(GIT_DIR)] && [info exists env(GIT_WORK_TREE)]} {
+		# Validate GIT_DIR by resolving its absolute path
+		if {[catch {set _abs [git rev-parse --absolute-git-dir]} err]} {
+			catch {wm withdraw .}
+			error_popup [strcat [mc "Invalid GIT_DIR:"] "\n\n$err"]
+			exit 1
+		}
+
+		# Change current directory to GIT_WORK_TREE
+		if {[catch {cd $env(GIT_WORK_TREE)} err]} {
+			catch {wm withdraw .}
+			error_popup [strcat [mc "Cannot change to GIT_WORK_TREE:"] "\n\n$err"]
+			exit 1
+		}
+
+		# Verify that GIT_WORK_TREE is a valid Git worktree
+		if {[catch {git rev-parse --show-toplevel} err]} {
+			catch {wm withdraw .}
+			error_popup [strcat [mc "GIT_WORK_TREE is not a valid worktree:"] "\n\n$err"]
+			exit 1
+		}
+
+		# Use repository path specified by environment variables
+		set _gitdir $_abs
+		set ::_prefix {}
+
+		# Unset GIT_WORK_TREE to prevent it from being inherited by child processes
+		unset env(GIT_WORK_TREE)
+
+		return 1
+	}
+
+	# Other cases (only GIT_DIR, only GIT_WORK_TREE, or neither):
+	# Do nothing here and let the existing logic handle it later
+	return 0
+}
+set force_gitdir [resolve_initial_environment]
+
 set picked 0
-if {[catch {
-		set _gitdir $env(GIT_DIR)
-		set _prefix {}
-		}]
-	&& [catch {
-		# beware that from the .git dir this sets _gitdir to .
-		# and _prefix to the empty string; this is handled by
-		# the startup safety checks below
-		set _gitdir [git rev-parse --git-dir]
-		set _prefix [git rev-parse --show-prefix]
-	} err]} {
-	load_config 1
-	apply_config
-	choose_repository::pick
-	if {![file isdirectory $_gitdir]} {
-		exit 1
+if {!$force_gitdir} {
+	if {[catch {
+			set _gitdir $env(GIT_DIR)
+			set _prefix {}
+			}]
+		&& [catch {
+			# beware that from the .git dir this sets _gitdir to .
+			# and _prefix to the empty string; this is handled by
+			# the startup safety checks below
+			set _gitdir [git rev-parse --git-dir]
+			set _prefix [git rev-parse --show-prefix]
+		} err]} {
+		load_config 1
+		apply_config
+		choose_repository::pick
+		if {![file isdirectory $_gitdir]} {
+			exit 1
+		}
+		set picked 1
 	}
-	set picked 1
 }
 
 if {![file isdirectory $_gitdir]} {
@@ -1228,7 +1272,11 @@ if {[lindex $_reponame end] eq {.git}} {
 
 # Export the final paths
 set env(GIT_DIR) $_gitdir
-set env(GIT_WORK_TREE) $_gitworktree
+# Do not export `GIT_WORK_TREE`. The current directory is already
+# the top-level of the working tree (if it exists), and Git will
+# automatically identify the working tree. For bare repositories,
+# `_gitworktree` is empty, and exporting an empty value would cause
+# commands like `git branch --show-current` to fail
 
 ######################################################################
 ##
-- 
2.52.0.windows.1