]> git.ipfire.org Git - thirdparty/git.git/commitdiff
git-gui: try harder to find worktree from gitdir
authorMark Levedahl <mlevedahl@gmail.com>
Sun, 31 May 2026 23:02:22 +0000 (19:02 -0400)
committerJohannes Sixt <j6t@kdbg.org>
Tue, 2 Jun 2026 17:13:13 +0000 (19:13 +0200)
git-gui, since 87cd09f43e ("git-gui: work from the .git dir",
2010-01-23), has had the intent to allow starting from inside a
repository, then switching to the parent directory if that is a valid
worktree.

This certainly hasn't worked since 2d92ab32fd ("rev-parse: make
--show-toplevel without a worktree an error", 2019-11-19) in git, but
breaking this git-gui feature was unintentional.

There are (at least) 3 cases where the gitdir can tell us where the
worktree is, and we would like all to work:

- core.worktree is set, and points to a valid worktree. This is already
  handled  by git rev-parse --show-toplevel, even when not in the worktree.
  There is nothing more to do in this case.

- the gitdir is embedded in a worktree as subdirectory .git. The parent
  is (or at least should be) a valid worktree. This worked long ago.

- the gitdir is a worktree specific directory (under
  <mainrepo>/worktrees/worktree_name), within which there is a file
  "gitdir" pointing to .git in the worktree. git gui never learned to
  handle this case.

Let's handle the latter two cases. Always check that the discovered
worktree is valid and points to the already discovered gitdir according
to git rev-parse. This avoids issues that may arise because we are
discovering from the gitdir up, rather than the worktree down, and file
system non-posix behavior or misconfiguration of git might cause
confusion.  For instance, a manually moved worktree might not be where
the gitdir points, or the gitdir might be configured with
core.bare=true.

Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
git-gui.sh

index 44dcb5ffaf34fbf0073e3bc629fab15496f535f8..57d48cc8387fb99ef1d67abd72262998de6d7e70 100755 (executable)
@@ -1103,6 +1103,37 @@ unset argv0dir
 ##
 ## repository setup
 
+proc find_worktree_from_gitdir {} {
+       # this is invoked only if the current directory is inside the repository
+       set worktree {}
+       if {[file tail $::_gitdir] eq {.git}} {
+               # the dir containing .git is a worktree if repo allows it
+               # Check that git reports parent as a worktree (gitdir might not allow a worktree)
+               if {[catch {
+                               set parent [file dirname $::_gitdir]
+                               set worktree [git -C $parent rev-parse --show-toplevel]
+                       }]} {
+                       set worktree {}
+               }
+       } elseif [file exists {gitdir}] {
+               # a worktree gitdir has .gitdir naming worktree/.git
+               # assure git run there reports this dir as the gitdir (links might be broken)
+               if {[catch {
+                               set fd_gitdir [open {gitdir} {r}]
+                               set worktree [file dirname [read $fd_gitdir]]
+                               catch {close $fd_gitdir}
+                               set worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir]
+                               if {$::_gitdir ne $worktree_gitdir} {
+                                       set worktree {}
+                               }
+                       }]} {
+                       catch {close $fd_gitdir}
+                       set worktree {}
+               }
+       }
+       return $worktree
+}
+
 proc is_gitvars_error {err} {
        set havevars 0
        set GIT_DIR {}
@@ -1176,6 +1207,13 @@ if {[catch {
        set _prefix {}
 }
 
+if {[is_bare]} {
+       # Maybe we are in an embedded or worktree specific gitdir
+       if {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {
+               set _prefix {}
+       }
+}
+
 if {![is_bare]} {
        if {[catch {cd $_gitworktree} err]} {
                catch {wm withdraw .}