[PATCH v7 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early
- From
- Shroom Moo <egg_mushroomcow@foxmail.com>
- Date
- May 9, 2026, 13:37 UTC
- Message-ID
- <tencent_C4AD92361C8D7B76EB4C8A6F14EA33496805@qq.com>
- In-Reply-To
- <20260509133756.1367-1-egg_mushroomcow@foxmail.com>
Users expect these two invocations to be equivalent:
GIT_WORK_TREE=/some/path GIT_DIR=/some/path/.git git gui
git -C /some/path guiCurrently, 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. Export the absolute GIT_DIR before changing directory to ensure Git commands see the intended repository.
- Introduce set_worktree_env and unset_worktree_env helper procedures to safely manage the GIT_WORK_TREE environment variable: set_worktree_env sets it only when $_gitworktree is non-empty, and unset_worktree_env removes the variable without error. Replace all direct manipulations of GIT_WORK_TREE with these helpers to avoid accidentally exporting an empty value or causing 'unset' errors.
- At the end of startup, use set_worktree_env instead of exporting GIT_WORK_TREE unconditionally. This leaves the variable unset when no worktree exists (bare repository), fixing commands that would fail with an empty GIT_WORK_TREE.
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 <mlevedahl@gmail.com> Helped-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com> --- git-gui/git-gui.sh | 103 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 24 deletions(-)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index ff022c5bbb..1123f9855b 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -1122,25 +1122,82 @@ unset argv0dir ## ## repository setup +# Safely set/unset GIT_WORK_TREE +proc set_worktree_env {} { + global _gitworktree + if {$_gitworktree ne {}} { + set ::env(GIT_WORK_TREE) $_gitworktree + } +} + +proc unset_worktree_env {} { + catch {unset ::env(GIT_WORK_TREE)} +} + +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 + } + set env(GIT_DIR) $_abs + + # 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_worktree_env + + 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,9 +1285,7 @@ if {[lindex $_reponame end] eq {.git}} { # Export the final paths set env(GIT_DIR) $_gitdir -if {$_gitworktree ne {}} { - set env(GIT_WORK_TREE) $_gitworktree -} +set_worktree_env ###################################################################### ## @@ -2077,12 +2132,12 @@ proc do_gitk {revs {is_submodule false}} { # by setting these to the appropriate values to allow gitk # to skip the heuristics to find their proper value unset env(GIT_DIR) - unset env(GIT_WORK_TREE) + unset_worktree_env } safe_exec_bg [concat $cmd $revs "--" "--"] set env(GIT_DIR) $_gitdir - set env(GIT_WORK_TREE) $_gitworktree + set_worktree_env cd $pwd if {[info exists main_status]} { @@ -2111,7 +2166,7 @@ proc do_git_gui {} { # see note in do_gitk about unsetting these vars when # running tools in a submodule unset env(GIT_DIR) - unset env(GIT_WORK_TREE) + unset_worktree_env set pwd [pwd] cd $current_diff_path @@ -2119,7 +2174,7 @@ proc do_git_gui {} { safe_exec_bg [concat $exe gui] set env(GIT_DIR) $_gitdir - set env(GIT_WORK_TREE) $_gitworktree + set_worktree_env cd $pwd set status_operation [$::main_status \
-- 2.52.0.windows.1