{"thread":{"id":"65531","subject":"[PATCH] git-gui: handle bare repo or missing worktree","startedAt":"2026-04-21T16:35:06Z","lastAt":"2026-06-02T21:08:40Z","messageCount":134,"participants":["Shroom Moo","Johannes Sixt","Mark Levedahl","Aina Boot"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542048","messageId":"tencent_3FF7B35FE8AD7236FC9BBB628B45EA65F405@qq.com","threadId":"65531","inReplyTo":null,"subject":"[PATCH] git-gui: handle bare repo or missing worktree","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-04-21T16:28:53Z","receivedAt":"2026-04-21T16:35:06Z","isPatch":true,"body":"When starting git-gui from a directory that Git recognizes as a valid\nrepository but the repository is either bare or its working tree is\nmissing, git-gui previously attempted to execute 'rev-parse\n--show-toplevel' without error handling. This caused a fatal Tcl error\n(\"this operation must be run in a work tree\") and prevented the user\nfrom opening the repository selection dialog.\n\nImprove the repository setup logic:\n- After obtaining the git directory via 'rev-parse --git-dir', check\n  whether the repository is bare or if the working tree can be\n  successfully located.\n- If the repository is unusable as a working tree, display a warning\n  and present the \"Create/Clone/Open\" repository selection dialog.\n\nThis makes git-gui robust when launched from a bare repository, from\ninside a .git directory without a worktree, or when GIT_DIR points to\nan invalid location. No regressions observed in normal working trees.\n\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 75 +++++++++++++++++++++++++++++++++++-----------\n 1 file changed, 57 insertions(+), 18 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..2e4bc2f226 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1123,24 +1123,63 @@ unset argv0dir\n ## repository setup\n \n set picked 0\n-if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n-\t} err]} {\n-\tload_config 1\n-\tapply_config\n-\tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n-\t\texit 1\n-\t}\n-\tset picked 1\n-}\n+# Save temporarily for restoration later\n+set _startup_cwd [pwd]\n+\n+set need_pick 0\n+if {[catch {set _gitdir $env(GIT_DIR); set _prefix {}}]} {\n+    # GIT_DIR not set in environment, attempt auto-detection\n+\t# beware that from the .git dir this sets _gitdir to .\n+\t# and _prefix to the empty string\n+    if {[catch {set _gitdir [git rev-parse --git-dir]; set _prefix [git rev-parse --show-prefix]} err]} {\n+        # Not inside any Git repository, proceed to repository selection UI\n+        set need_pick 1\n+    } else {\n+        # Inside a Git repository, but need to verify its usability\n+        cd [file dirname $_gitdir]\n+        set bare 0\n+        set worktree_missing 0\n+\t\t# Check if this is a bare repository (no working tree)\n+        if {![catch {set bare [git rev-parse --is-bare-repository]}]} {\n+            if {$bare eq \"true\"} {\n+                set bare 1\n+            }\n+        }\n+\t\t# Check if working tree is present and accessible\n+        if {[catch {git rev-parse --show-toplevel}]} {\n+            set worktree_missing 1\n+        }\n+\n+        cd $_startup_cwd\n+\n+        # For bare repos or missing worktrees, warn and guide to selection\n+        if {$bare || $worktree_missing} {\n+            set msg [mc \"The repository at '%s' cannot be opened:\" [file normalize $_gitdir]]\n+            if {$bare} {\n+                append msg \"\\n\\n\" [mc \"It is a bare repository (no working tree).\"]\n+            } else {\n+                append msg \"\\n\\n\" [mc \"The working tree appears to be missing or inaccessible.\"]\n+            }\n+            append msg \"\\n\\n\" [mc \"Please select a valid working repository, or create/clone one.\"]\n+            tk_messageBox -icon warning -type ok -title [mc \"Repository Unusable\"] -message $msg\n+\n+            set need_pick 1\n+        }\n+    }\n+}\n+\n+if {$need_pick} {\n+    load_config 1\n+    apply_config\n+    choose_repository::pick\n+    if {![file isdirectory $_gitdir]} {\n+        exit 1\n+    }\n+    set picked 1\n+}\n+\n+# Clean up working tree checking temporary variables\n+unset -nocomplain _startup_cwd need_pick bare worktree_missing\n \n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n set hashalgorithm [git rev-parse --show-object-format]\n-- \n2.52.0.windows.1\n\n"},{"id":"542460","messageId":"308229f2-0d20-4f3b-8e6a-c962bd9c3a6e@kdbg.org","threadId":"65531","inReplyTo":"tencent_3FF7B35FE8AD7236FC9BBB628B45EA65F405@qq.com","subject":"Re: [PATCH] git-gui: handle bare repo or missing worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-04-29T06:58:58Z","receivedAt":"2026-04-29T07:38:15Z","isPatch":true,"body":"Am 21.04.26 um 18:28 schrieb Shroom Moo:\n> When starting git-gui from a directory that Git recognizes as a valid\n> repository but the repository is either bare or its working tree is\n> missing, git-gui previously attempted to execute 'rev-parse\n> --show-toplevel' without error handling. This caused a fatal Tcl error\n> (\"this operation must be run in a work tree\") and prevented the user\n> from opening the repository selection dialog.\n> \n> Improve the repository setup logic:\n> - After obtaining the git directory via 'rev-parse --git-dir', check\n>   whether the repository is bare or if the working tree can be\n>   successfully located.\n> - If the repository is unusable as a working tree, display a warning\n>   and present the \"Create/Clone/Open\" repository selection dialog.\n\nI consider it harmful to allow the user to create (or clone) a\nrepository after Git GUI has already determined the existence of a\nrepository. If the worktree was not found, we should report an error and\nexit after the message is dismissed, and not give the option to create a\nrepository.\n\n> \n> This makes git-gui robust when launched from a bare repository, from\n> inside a .git directory without a worktree, or when GIT_DIR points to\n> an invalid location. No regressions observed in normal working trees.\n> \n> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n> ---\n>  git-gui/git-gui.sh | 75 +++++++++++++++++++++++++++++++++++-----------\n>  1 file changed, 57 insertions(+), 18 deletions(-)\n> \n> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n> index 23fe76e498..2e4bc2f226 100755\n> --- a/git-gui/git-gui.sh\n> +++ b/git-gui/git-gui.sh\n> @@ -1123,24 +1123,63 @@ unset argv0dir\n>  ## repository setup\n>  \n>  set picked 0\n> -if {[catch {\n> -\t\tset _gitdir $env(GIT_DIR)\n> -\t\tset _prefix {}\n> -\t\t}]\n> -\t&& [catch {\n> -\t\t# beware that from the .git dir this sets _gitdir to .\n> -\t\t# and _prefix to the empty string\n> -\t\tset _gitdir [git rev-parse --git-dir]\n> -\t\tset _prefix [git rev-parse --show-prefix]\n> -\t} err]} {\n> -\tload_config 1\n> -\tapply_config\n> -\tchoose_repository::pick\n> -\tif {![file isdirectory $_gitdir]} {\n> -\t\texit 1\n> -\t}\n> -\tset picked 1\n> -}\n> +# Save temporarily for restoration later\n> +set _startup_cwd [pwd]\n> +\n> +set need_pick 0\n> +if {[catch {set _gitdir $env(GIT_DIR); set _prefix {}}]} {\n> +    # GIT_DIR not set in environment, attempt auto-detection\n> +\t# beware that from the .git dir this sets _gitdir to .\n> +\t# and _prefix to the empty string\n> +    if {[catch {set _gitdir [git rev-parse --git-dir]; set _prefix [git rev-parse --show-prefix]} err]} {\n> +        # Not inside any Git repository, proceed to repository selection UI\n> +        set need_pick 1\n> +    } else {\n> +        # Inside a Git repository, but need to verify its usability\n> +        cd [file dirname $_gitdir]\n> +        set bare 0\n> +        set worktree_missing 0\n> +\t\t# Check if this is a bare repository (no working tree)\n> +        if {![catch {set bare [git rev-parse --is-bare-repository]}]} {\n> +            if {$bare eq \"true\"} {\n> +                set bare 1\n> +            }\n> +        }\n> +\t\t# Check if working tree is present and accessible\n> +        if {[catch {git rev-parse --show-toplevel}]} {\n> +            set worktree_missing 1\n> +        }\n> +\n> +        cd $_startup_cwd\n> +\n> +        # For bare repos or missing worktrees, warn and guide to selection\n> +        if {$bare || $worktree_missing} {\n> +            set msg [mc \"The repository at '%s' cannot be opened:\" [file normalize $_gitdir]]\n> +            if {$bare} {\n> +                append msg \"\\n\\n\" [mc \"It is a bare repository (no working tree).\"]\n> +            } else {\n> +                append msg \"\\n\\n\" [mc \"The working tree appears to be missing or inaccessible.\"]\n> +            }\n> +            append msg \"\\n\\n\" [mc \"Please select a valid working repository, or create/clone one.\"]\n> +            tk_messageBox -icon warning -type ok -title [mc \"Repository Unusable\"] -message $msg\n> +\n> +            set need_pick 1\n> +        }\n> +    }\n> +}\n> +\n> +if {$need_pick} {\n> +    load_config 1\n> +    apply_config\n> +    choose_repository::pick\n> +    if {![file isdirectory $_gitdir]} {\n> +        exit 1\n> +    }\n> +    set picked 1\n> +}\n> +\n> +# Clean up working tree checking temporary variables\n> +unset -nocomplain _startup_cwd need_pick bare worktree_missing\n\nThis adds quite a lot of code with failure cases, but after this point\nwe already have some quite extensive error diagnosis, too. Except that\nthe call to git rev-parse --show-toplevel is not protected.\n\nIf we do not invoke the Create/Clone/Open dialog after a repository has\nalready been discovered, would it not be sufficient to just add the\nprotection around the --show-toplevel call? Or is there something else\nmissing in the existing error paths?\n\n-- Hannes\n\n"},{"id":"542479","messageId":"tencent_AEE968E8E785907BA55A383977C8968ED406@qq.com","threadId":"65531","inReplyTo":"308229f2-0d20-4f3b-8e6a-c962bd9c3a6e@kdbg.org","subject":"[PATCH v2 1/1] git-gui: protect rev-parse --show-toplevel call","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-04-29T17:32:20Z","receivedAt":"2026-04-29T17:33:47Z","isPatch":true,"body":"When starting git-gui from a directory that is a bare repository or\nwhere the working tree is missing, git-gui previously executed\n'rev-parse --show-toplevel' without error handling. This caused a\nfatal Tcl error (\"this operation must be run in a work tree\").\n\nWrap the call in a catch to prevent the fatal error. The existing\nerror paths after this call already handle bare repos and missing\nworktrees appropriately.\n\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..aee37685e1 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1169,7 +1169,9 @@ if {![file isdirectory $_gitdir]} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n+if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n+    set _gitworktree {}\n+}\n \n if {$_prefix ne {}} {\n \tif {$_gitworktree eq {}} {\n-- \n2.52.0.windows.1\n\n"},{"id":"542480","messageId":"tencent_38DC315B635248A3A19F1A426A95A3E19208@qq.com","threadId":"65531","inReplyTo":"308229f2-0d20-4f3b-8e6a-c962bd9c3a6e@kdbg.org","subject":"Re: [PATCH] git-gui: handle bare repo or missing worktree","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-04-29T18:28:22Z","receivedAt":"2026-04-29T18:29:32Z","isPatch":true,"body":"Hi Hannes, \n\nThanks for taking a close look at the patch.  You're right that the \nminimal crashfix is just protecting the `rev-parse --show-toplevel` \ncall.  The existing error paths after that line already handle bare \nrepos and missing worktrees, showing an error dialog before exiting. \nThat alone would prevent the fatal Tcl error –the original crash is \nfixed. \n\nThe extra logic is not about incomplete error diagnosis (the other \nchecks cover that).  It's about improving the user experience in the \nspecific case that prompted this report: a colleague double-clicked \ngit-gui.exe inside the Git installation directory, where a third-party \nprogram had accidentally left a HEAD file.  That caused git to treat \nthe directory as a bare repository, even though it has no . git folder. \nIn that context, showing a \"Cannot use bare repository\" and quitting \nwas confusing – the user simply started the program without choosing \nthat path. \n\nSo the additional detection and the `choose_repository::pick` call are \na UX improvement, not a correctness requirement.  When the program \ndetects that the current directory is an unusable repository, falling \nback to the repository-selection dialog (as we do when no repository \nis found) is better than failing with an error.  The user can pick a \nvalid repository and continue without restarting the GUI. \n\nSince a minimal patch is preferred, I have prepared v2 which only \nprotects `--show-toplevel`.  The dialog fallback could be revisited as \na separate enhancement if desired. I've already sent the v2 patch to \nthe list.\n\n-- Shroom\n"},{"id":"542481","messageId":"0100848a-d408-49f0-aeb1-8835c8775539@gmail.com","threadId":"65531","inReplyTo":"tencent_AEE968E8E785907BA55A383977C8968ED406@qq.com","subject":"Re: [PATCH v2 1/1] git-gui: protect rev-parse --show-toplevel call","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-04-29T20:14:06Z","receivedAt":"2026-04-29T20:14:11Z","isPatch":true,"body":"On 4/29/26 1:32 PM, Shroom Moo wrote:\n> When starting git-gui from a directory that is a bare repository or\n> where the working tree is missing, git-gui previously executed\n> 'rev-parse --show-toplevel' without error handling. This caused a\n> fatal Tcl error (\"this operation must be run in a work tree\").\n> \n> Wrap the call in a catch to prevent the fatal error. The existing\n> error paths after this call already handle bare repos and missing\n> worktrees appropriately.\n> \n> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n> ---\n>  git-gui/git-gui.sh | 4 +++-\n>  1 file changed, 3 insertions(+), 1 deletion(-)\n> \n> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n> index 23fe76e498..aee37685e1 100755\n> --- a/git-gui/git-gui.sh\n> +++ b/git-gui/git-gui.sh\n> @@ -1169,7 +1169,9 @@ if {![file isdirectory $_gitdir]} {\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n> +if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n> +    set _gitworktree {}\n> +}\n>  \n>  if {$_prefix ne {}} {\n>  \tif {$_gitworktree eq {}} {\n\nUnfortunately, this allows starting git-gui inside the separate gitdir created by\n\n\tgit clone --separate-git-dir=/some/where/else ...\n\nThere is no hint where the workdir is, but git recognizes the repository is not bare:\n\n\tgit rev-parse --is-bare-repository  ==> false\n\tgit rev-parse --is-inside-git-dir   ==> true\n\tgit rev-parse --is-inside-work-tree ==> false\n\tgit rev-parse --show-toplevel       ==> fatal: must be run in a work tree\n\tgit worktree list                   ==> absolute path to gitdir (not a worktree)\n\nAs git-gui has no protection against modifying what is effectively a bare repository,\nallowing git-gui to run in this directory is dangerous, or possibly just very confusing.\n\ngit refuses to work in this gitdir: \"git status\" run in the above gitdir gives: \"fatal:\nthis operation must be run in a work tree.\"\n\nThe simplest safe thing is to catch the error and abort with a more useful message than\ncurrently provided. Or perhaps, check git rev-parse --is-inside-git-dir and abort, and do\nso before trying --show-toplevel.\n\n\nMark\n\n"},{"id":"542523","messageId":"tencent_8A236D9D4A8D8CCA7DAA083157AA8543700A@qq.com","threadId":"65531","inReplyTo":"tencent_AEE968E8E785907BA55A383977C8968ED406@qq.com","subject":"[PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-04-30T10:02:18Z","receivedAt":"2026-04-30T10:03:41Z","isPatch":true,"body":"When git-gui is started from a directory that Git recognizes as a\nvalid repository but the working tree is not accessible (e.g., a\nseparated gitdir created by `git clone --separate-git-dir`, a bare\nrepository, or a case where the worktree directory was removed),\nit previously called `rev-parse --show-toplevel` without error\nhandling, causing a fatal Tcl error (\"this operation must be run\nin a work tree\").\n\nWrap the call in a `catch` and handle the failure as follows:\n\n- For bare repositories, keep `_gitworktree` empty so that the\n  existing `is_bare` check shows \"Cannot use bare repository\" and\n  exits.  No behavioral change.\n\n- For non‑bare repositories, try to locate the worktree from the\n  parent directory using `git -C $parent rev-parse --show-toplevel`.\n  If the parent is a valid worktree, change to it; this covers the\n  legitimate case of starting git-gui from within the .git\n  subdirectory of a normal working tree.\n\n- If the parent directory is not a worktree, refuse to start with\n  a clear error message.  This prevents dangerous operations in a\n  separated gitdir, where ordinary Git commands like `git status`\n  would themselves refuse to run.\n\nThe approach intentionally avoids two pitfalls:\n\n  - Testing `--is-inside-git-dir` before calling `--show-toplevel`\n    would break the normal use case of starting git-gui from within\n    a .git subdirectory (where --show-toplevel would succeed).\n\n  - A simple “non‑bare” check after a failed --show-toplevel would\n    reject a normal repository whose worktree was only temporarily\n    removed.\n\nThe chosen method keeps the original behavior for bare repositories\nand for regular working trees, fixes the crash, and properly blocks\nseparated gitdirs without a reachable worktree.\n\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 23 ++++++++++++++++++++++-\n 1 file changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..2392282df3 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1169,7 +1169,28 @@ if {![file isdirectory $_gitdir]} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n+if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n+\t# For bare repositories, use the existing error handling\n+\tif {![catch {set bare [git rev-parse --is-bare-repository]}] && $bare eq {true}} {\n+\t\tset _gitworktree {}\n+\t} else {\n+\t\t# Non-bare: try to find the worktree from the parent directory\n+\t\tset parent [file dirname [pwd]]\n+\t\t# Cannot go higher than the root directory; leave _gitworktree empty\n+\t\tif {[file normalize $parent] eq [file normalize [pwd]]} {\n+\t\t\t# Already at the filesystem root; let existing paths cope\n+\t\t\tset _gitworktree {}\n+\t\t} elseif {![catch {\n+\t\t\tset _gitworktree [git -C $parent rev-parse --show-toplevel]\n+\t\t}]} {\n+\t\t\tcd $parent\n+\t\t} else {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [mc \"Cannot start git-gui from inside the Git directory.\"]\n+\t\t\texit 1\n+\t\t}\n+\t}\n+}\n \n if {$_prefix ne {}} {\n \tif {$_gitworktree eq {}} {\n-- \n2.52.0.windows.1\n\n"},{"id":"542535","messageId":"3b0b37ed-1a5d-4fe1-b2b4-7db67a62a06d@gmail.com","threadId":"65531","inReplyTo":"tencent_8A236D9D4A8D8CCA7DAA083157AA8543700A@qq.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-04-30T16:18:42Z","receivedAt":"2026-04-30T16:18:44Z","isPatch":true,"body":"\n\nOn 4/30/26 6:02 AM, Shroom Moo wrote:\n> When git-gui is started from a directory that Git recognizes as a\n> valid repository but the working tree is not accessible (e.g., a\n> separated gitdir created by `git clone --separate-git-dir`, a bare\n> repository, or a case where the worktree directory was removed),\n> it previously called `rev-parse --show-toplevel` without error\n> handling, causing a fatal Tcl error (\"this operation must be run\n> in a work tree\").\n>\n> Wrap the call in a `catch` and handle the failure as follows:\n>\n> - For bare repositories, keep `_gitworktree` empty so that the\n>   existing `is_bare` check shows \"Cannot use bare repository\" and\n>   exits.  No behavioral change.\n>\n> - For non‑bare repositories, try to locate the worktree from the\n>   parent directory using `git -C $parent rev-parse --show-toplevel`.\n>   If the parent is a valid worktree, change to it; this covers the\n>   legitimate case of starting git-gui from within the .git\n>   subdirectory of a normal working tree.\n>\n> - If the parent directory is not a worktree, refuse to start with\n>   a clear error message.  This prevents dangerous operations in a\n>   separated gitdir, where ordinary Git commands like `git status`\n>   would themselves refuse to run.\n>\n> The approach intentionally avoids two pitfalls:\n>\n>   - Testing `--is-inside-git-dir` before calling `--show-toplevel`\n>     would break the normal use case of starting git-gui from within\n>     a .git subdirectory (where --show-toplevel would succeed).\n>\n>   - A simple “non‑bare” check after a failed --show-toplevel would\n>     reject a normal repository whose worktree was only temporarily\n>     removed.\n>\n> The chosen method keeps the original behavior for bare repositories\n> and for regular working trees, fixes the crash, and properly blocks\n> separated gitdirs without a reachable worktree.\n>\n> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n> ---\n>  git-gui/git-gui.sh | 23 ++++++++++++++++++++++-\n>  1 file changed, 22 insertions(+), 1 deletion(-)\n>\n> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n> index 23fe76e498..2392282df3 100755\n> --- a/git-gui/git-gui.sh\n> +++ b/git-gui/git-gui.sh\n> @@ -1169,7 +1169,28 @@ if {![file isdirectory $_gitdir]} {\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n> +if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n> +\t# For bare repositories, use the existing error handling\n> +\tif {![catch {set bare [git rev-parse --is-bare-repository]}] && $bare eq {true}} {\n> +\t\tset _gitworktree {}\n> +\t} else {\n> +\t\t# Non-bare: try to find the worktree from the parent directory\n> +\t\tset parent [file dirname [pwd]]\n> +\t\t# Cannot go higher than the root directory; leave _gitworktree empty\n> +\t\tif {[file normalize $parent] eq [file normalize [pwd]]} {\n> +\t\t\t# Already at the filesystem root; let existing paths cope\n> +\t\t\tset _gitworktree {}\n> +\t\t} elseif {![catch {\n> +\t\t\tset _gitworktree [git -C $parent rev-parse --show-toplevel]\n> +\t\t}]} {\n> +\t\t\tcd $parent\n> +\t\t} else {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [mc \"Cannot start git-gui from inside the Git directory.\"]\n> +\t\t\texit 1\n> +\t\t}\n> +\t}\n> +}\n>  \n>  if {$_prefix ne {}} {\n>  \tif {$_gitworktree eq {}} {\n\nA bare repository can be contained in a workdir / worktree pointing at a different gitdir:\nthe logic above can thus a workdir that doesn't use the gitdir where git-gui was started.\nThe bigger issue is that a gitdir can support multiple checked-out directories with no\none-to-one mapping and no clear idea of which of those a user may have intended.\n\nSo, I believe the correct fix is to test \"rev-parse --is-inside-git-dir, and if so throw a\nclear error message and exit. This will give the user something to start with to solve the\nproblem of why they started git-gui in a gitdir, and not in a worktree.\n\nMark\n\n"},{"id":"542545","messageId":"tencent_CCA549318D2A7DC6095954F2858371AB6D0A@qq.com","threadId":"65531","inReplyTo":"3b0b37ed-1a5d-4fe1-b2b4-7db67a62a06d@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-01T10:22:31Z","receivedAt":"2026-05-01T10:54:21Z","isPatch":true,"body":"Hi Mark,\n\nThanks for catching the multi‑worktree ambiguity. The parent‑directory\nfallback can surely accidentally pick the wrong worktree. As a \nreminder, the current code deliberately supports starting git-gui from \nwithin a regular repository's .git directory.\n\nThe comment says:\n# beware that from the .git dir this sets _gitdir to .\n# and _prefix to the empty string\n\nIn that case, _gitdir is \".\", _prefix is empty, and the later logic\nfalls back to using [file dirname $_gitdir] as the worktree. A blanket\n\"if --is-inside-git-dir then exit\" would make that case useless.\n\nI'll send a v4 that first checks --is-bare-repository (preserving the\noriginal bare‑repo error), then checks --is-inside-git-dir and refuses\nif inside a gitdir. This accepts the .git‑startup limitation in\nexchange for safety, and keeps the bare‑repo message unchanged.\n\nTwo alternatives still exist if a different trade‑off is preferred: \n- Only check --is-inside-git-dir (simpler, but makes the\nbare‑repo error_popup useless).\n- After --is-inside-git-dir, consult git worktree list and\nswitch to the single worktree if unambiguous (keeps .git‑startup\nbut adds complexity and a runtime dependency).\n\nShroom\n"},{"id":"542546","messageId":"tencent_277823B7C5D69914E168E5679A907C655606@qq.com","threadId":"65531","inReplyTo":"tencent_8A236D9D4A8D8CCA7DAA083157AA8543700A@qq.com","subject":"[PATCH v4 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-01T10:54:13Z","receivedAt":"2026-05-01T10:56:00Z","isPatch":true,"body":"When git-gui is started from a directory that Git recognizes as a\nvalid repository but the working tree is not accessible (e.g., a\nseparated gitdir created by `git clone --separate-git-dir`, a bare\nrepository, or a case where the worktree directory was removed),\nit previously called `rev-parse --show-toplevel` without error\nhandling, causing a fatal Tcl error (\"this operation must be run\nin a work tree\").\n\nWrap the call in a `catch` and handle the failure as follows:\n\n- For bare repositories, keep `_gitworktree` empty so that the\n  existing `is_bare` check shows \"Cannot use bare repository\" and\n  exits.  No behavioral change.\n\n- If we are inside the gitdir (i.e., `--is-inside-git-dir` is\n  `true`), refuse to start with a clear message: \"Cannot start\n  git-gui from inside the Git directory. Please run git-gui from a\n  working tree.\"  This prevents dangerous operations in a separated\n  gitdir or when starting from a .git subdirectory, where ordinary\n  Git commands like `git status` also refuse to run.\n\n- Otherwise, leave `_gitworktree` empty so that the existing error\n  paths (e.g., missing worktree) can display their own diagnostics.\n\nThis fix intentionally drops the previous ability to start git-gui\nfrom within a regular repository's .git subdirectory.  That behavior\nwas never reliable (Git itself forbids operations inside .git) and\nbroke completely in the multi‑worktree case where the parent\ndirectory is not guaranteed to be the intended worktree.  The error\nmessage guides the user to start git-gui from a worktree instead.\n\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..9e848aa26b 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1169,7 +1169,17 @@ if {![file isdirectory $_gitdir]} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n+if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n+    if {![catch {set bare [git rev-parse --is-bare-repository]}] && $bare eq {true}} {\n+        set _gitworktree {}\n+    } elseif {![catch {set inside [git rev-parse --is-inside-git-dir]}] && $inside eq {true}} {\n+        catch {wm withdraw .}\n+        error_popup [mc \"Cannot start git-gui from inside the Git directory\\nPlease run git-gui from a working tree\"]\n+        exit 1\n+    } else {\n+        set _gitworktree {}\n+    }\n+}\n \n if {$_prefix ne {}} {\n \tif {$_gitworktree eq {}} {\n-- \n2.52.0.windows.1\n\n"},{"id":"542547","messageId":"77219c75-7968-413f-a642-0446145c8023@kdbg.org","threadId":"65531","inReplyTo":"3b0b37ed-1a5d-4fe1-b2b4-7db67a62a06d@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-01T13:13:48Z","receivedAt":"2026-05-01T13:14:07Z","isPatch":true,"body":"Am 30.04.26 um 18:18 schrieb Mark Levedahl:\n> \n> \n> On 4/30/26 6:02 AM, Shroom Moo wrote:\n>> When git-gui is started from a directory that Git recognizes as a\n>> valid repository but the working tree is not accessible (e.g., a\n>> separated gitdir created by `git clone --separate-git-dir`, a bare\n>> repository, or a case where the worktree directory was removed),\n>> it previously called `rev-parse --show-toplevel` without error\n>> handling, causing a fatal Tcl error (\"this operation must be run\n>> in a work tree\").\n>>\n>> Wrap the call in a `catch` and handle the failure as follows:\n>>\n>> - For bare repositories, keep `_gitworktree` empty so that the\n>>   existing `is_bare` check shows \"Cannot use bare repository\" and\n>>   exits.  No behavioral change.\n>>\n>> - For non‑bare repositories, try to locate the worktree from the\n>>   parent directory using `git -C $parent rev-parse --show-toplevel`.\n>>   If the parent is a valid worktree, change to it; this covers the\n>>   legitimate case of starting git-gui from within the .git\n>>   subdirectory of a normal working tree.\n>>\n>> - If the parent directory is not a worktree, refuse to start with\n>>   a clear error message.  This prevents dangerous operations in a\n>>   separated gitdir, where ordinary Git commands like `git status`\n>>   would themselves refuse to run.\n>>\n>> The approach intentionally avoids two pitfalls:\n>>\n>>   - Testing `--is-inside-git-dir` before calling `--show-toplevel`\n>>     would break the normal use case of starting git-gui from within\n>>     a .git subdirectory (where --show-toplevel would succeed).\n>>\n>>   - A simple “non‑bare” check after a failed --show-toplevel would\n>>     reject a normal repository whose worktree was only temporarily\n>>     removed.\n>>\n>> The chosen method keeps the original behavior for bare repositories\n>> and for regular working trees, fixes the crash, and properly blocks\n>> separated gitdirs without a reachable worktree.\n>>\n>> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n>> ---\n>>  git-gui/git-gui.sh | 23 ++++++++++++++++++++++-\n>>  1 file changed, 22 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n>> index 23fe76e498..2392282df3 100755\n>> --- a/git-gui/git-gui.sh\n>> +++ b/git-gui/git-gui.sh\n>> @@ -1169,7 +1169,28 @@ if {![file isdirectory $_gitdir]} {\n>>  load_config 0\n>>  apply_config\n>>  \n>> -set _gitworktree [git rev-parse --show-toplevel]\n>> +if {[catch {set _gitworktree [git rev-parse --show-toplevel]}]} {\n>> +\t# For bare repositories, use the existing error handling\n>> +\tif {![catch {set bare [git rev-parse --is-bare-repository]}] && $bare eq {true}} {\n>> +\t\tset _gitworktree {}\n>> +\t} else {\n>> +\t\t# Non-bare: try to find the worktree from the parent directory\n>> +\t\tset parent [file dirname [pwd]]\n>> +\t\t# Cannot go higher than the root directory; leave _gitworktree empty\n>> +\t\tif {[file normalize $parent] eq [file normalize [pwd]]} {\n>> +\t\t\t# Already at the filesystem root; let existing paths cope\n>> +\t\t\tset _gitworktree {}\n>> +\t\t} elseif {![catch {\n>> +\t\t\tset _gitworktree [git -C $parent rev-parse --show-toplevel]\n>> +\t\t}]} {\n>> +\t\t\tcd $parent\n>> +\t\t} else {\n>> +\t\t\tcatch {wm withdraw .}\n>> +\t\t\terror_popup [mc \"Cannot start git-gui from inside the Git directory.\"]\n>> +\t\t\texit 1\n>> +\t\t}\n>> +\t}\n>> +}\n>>  \n>>  if {$_prefix ne {}} {\n>>  \tif {$_gitworktree eq {}} {\n> \n> A bare repository can be contained in a workdir / worktree pointing at a different gitdir:\n> the logic above can thus a workdir that doesn't use the gitdir where git-gui was started.\n> The bigger issue is that a gitdir can support multiple checked-out directories with no\n> one-to-one mapping and no clear idea of which of those a user may have intended.\n> \n> So, I believe the correct fix is to test \"rev-parse --is-inside-git-dir, and if so throw a\n> clear error message and exit. This will give the user something to start with to solve the\n> problem of why they started git-gui in a gitdir, and not in a worktree.\nWe have quite a bit of code that attempts to make Git GUI work from the\n.git directory and also in bare repositories.\n\n87cd09f43e56 (\"git-gui: work from the .git dir\", 2010-01-23) made the\nfirst step. The original code just used the $_gitdir as the working\ndirectory. However, at that time we did not have alternate worktrees,\nand the old code, when used today, does not work in a `git\nworktree`-created worktree. Later, the `git rev-parse --show-toplevel`\ncall came with 38ec8d3e2652 (\"git-gui: correct assignment of work-tree\",\n2010-10-20). However, it also changes the fall-back code slightly, so\nthat running Git GUI from the .git directory would not work the same way\nas before and takes the .git directory as the work tree (because in the\n.git directory --show-cdup is not \"..\", but empty).\n\nI think we need to restructure the existing flow a bit and not just fix\na single spot in the code. I suggest this order of operation:\n\n1. Handle the bare repository case. If not enabled, fail. Otherwise, we\ncan work with an empty $_gitworktree.\n\n2. Collect --show-toplevel into $_gitworktree.\n\n2a. If this failed: If --is-inside-git-dir is true, and the last\n$_gitdir directory component is exactly \".git\", take the parent\nrepository as $_gitworktree. Otherwise, fail.\n\n3. Handle all the other edge cases, if any, with the so determined\n$_gitworktree. (I didn't think through, yet, what needs to be done.)\n\n-- Hannes\n\n"},{"id":"542554","messageId":"a1a7237c-ffed-4a7a-ae58-55769aaa4453@gmail.com","threadId":"65531","inReplyTo":"77219c75-7968-413f-a642-0446145c8023@kdbg.org","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-01T16:42:39Z","receivedAt":"2026-05-01T16:42:43Z","isPatch":true,"body":"On 5/1/26 9:13 AM, Johannes Sixt wrote:\n> Am 30.04.26 um 18:18 schrieb Mark Levedahl:\n>>\n>> On 4/30/26 6:02 AM, Shroom Moo wrote:\n>> We have quite a bit of code that attempts to make Git GUI work from the\n> .git directory and also in bare repositories.\n>\n> 87cd09f43e56 (\"git-gui: work from the .git dir\", 2010-01-23) made the\n> first step. The original code just used the $_gitdir as the working\n> directory. However, at that time we did not have alternate worktrees,\n> and the old code, when used today, does not work in a `git\n> worktree`-created worktree. Later, the `git rev-parse --show-toplevel`\n> call came with 38ec8d3e2652 (\"git-gui: correct assignment of work-tree\",\n> 2010-10-20). However, it also changes the fall-back code slightly, so\n> that running Git GUI from the .git directory would not work the same way\n> as before and takes the .git directory as the work tree (because in the\n> .git directory --show-cdup is not \"..\", but empty).\n>\n> I think we need to restructure the existing flow a bit and not just fix\n> a single spot in the code. I suggest this order of operation:\n>\n> 1. Handle the bare repository case. If not enabled, fail. Otherwise, we\n> can work with an empty $_gitworktree.\n>\n> 2. Collect --show-toplevel into $_gitworktree.\n>\n> 2a. If this failed: If --is-inside-git-dir is true, and the last\n> $_gitdir directory component is exactly \".git\", take the parent\n> repository as $_gitworktree. Otherwise, fail.\n>\n> 3. Handle all the other edge cases, if any, with the so determined\n> $_gitworktree. (I didn't think through, yet, what needs to be done.)\n>\n> -- Hannes\n>\n\nI found one horrid edge case: \n\nStart git-gui in a gitdir not embedded in a worktree, with core.bare=false as there are\none or more gitfile and/or symlinked worktrees elsewhere.\n- current git-gui aborts with an uncaught error. Good.\n- git-gui with the wrapped --show-toplevel call finds no worktree to switch to, so runs in\nthe gitdir allowing commits of the gitdir items.\n-  I just added and  committed the *file* refs/heads/master to branch master in such a gitdir.\n\ngit-gui's normal gui must be started ONLY if rev-parse --is-inside-work-tree is true. (The\nblame view invoked by gitk in theory could be allowed to run in a bare repository\nread-only mode.).\n\nFor read/write mode:\nif --is-inside-git-dir == true at startup, we must abort, or find a valid worktree and\nswitch to that.\n\nMy personal preference is for git-gui to abort:\n   I started git-gui where it cannot run. \n   My error. Let me learn and fix that.\n\nAlternatively, ask me what to do:\n    e.g., prepare a dialog after looking at git-worktree list, and the parent dir IFF this\ndir is named .git, telling me of my mistake and offering me one or more worktrees to\nswitch to.\n\nBut please, don't just switch to another directory without asking. This is just\nencouraging me to make careless errors.\n\nMark\n\n"},{"id":"542595","messageId":"93e1c61f-e58b-4a0c-8ece-7a8d945fa900@gmail.com","threadId":"65531","inReplyTo":"a1a7237c-ffed-4a7a-ae58-55769aaa4453@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-02T21:51:03Z","receivedAt":"2026-05-02T21:51:05Z","isPatch":true,"body":"\n\nOn 5/1/26 12:42 PM, Mark Levedahl wrote:\n> On 5/1/26 9:13 AM, Johannes Sixt wrote:\n>> Am 30.04.26 um 18:18 schrieb Mark Levedahl:\n>>> On 4/30/26 6:02 AM, Shroom Moo wrote:\n>>> We have quite a bit of code that attempts to make Git GUI work from the\n>> .git directory and also in bare repositories.\n>>\n>> 87cd09f43e56 (\"git-gui: work from the .git dir\", 2010-01-23) made the\n>> first step. The original code just used the $_gitdir as the working\n>> directory. However, at that time we did not have alternate worktrees,\n>> and the old code, when used today, does not work in a `git\n>> worktree`-created worktree. Later, the `git rev-parse --show-toplevel`\n>> call came with 38ec8d3e2652 (\"git-gui: correct assignment of work-tree\",\n>> 2010-10-20). However, it also changes the fall-back code slightly, so\n>> that running Git GUI from the .git directory would not work the same way\n>> as before and takes the .git directory as the work tree (because in the\n>> .git directory --show-cdup is not \"..\", but empty).\n>>\n>> I think we need to restructure the existing flow a bit and not just fix\n>> a single spot in the code. I suggest this order of operation:\n>>\n>> 1. Handle the bare repository case. If not enabled, fail. Otherwise, we\n>> can work with an empty $_gitworktree.\n>>\n>> 2. Collect --show-toplevel into $_gitworktree.\n>>\n>> 2a. If this failed: If --is-inside-git-dir is true, and the last\n>> $_gitdir directory component is exactly \".git\", take the parent\n>> repository as $_gitworktree. Otherwise, fail.\n>>\n>> 3. Handle all the other edge cases, if any, with the so determined\n>> $_gitworktree. (I didn't think through, yet, what needs to be done.)\n>>\n>> -- Hannes\n>>\n> I found one horrid edge case: \n>\n> Start git-gui in a gitdir not embedded in a worktree, with core.bare=false as there are\n> one or more gitfile and/or symlinked worktrees elsewhere.\n> - current git-gui aborts with an uncaught error. Good.\n> - git-gui with the wrapped --show-toplevel call finds no worktree to switch to, so runs in\n> the gitdir allowing commits of the gitdir items.\n> -  I just added and  committed the *file* refs/heads/master to branch master in such a gitdir.\n>\n> git-gui's normal gui must be started ONLY if rev-parse --is-inside-work-tree is true. (The\n> blame view invoked by gitk in theory could be allowed to run in a bare repository\n> read-only mode.).\n>\n> For read/write mode:\n> if --is-inside-git-dir == true at startup, we must abort, or find a valid worktree and\n> switch to that.\n>\n> My personal preference is for git-gui to abort:\n>    I started git-gui where it cannot run. \n>    My error. Let me learn and fix that.\n>\n> Alternatively, ask me what to do:\n>     e.g., prepare a dialog after looking at git-worktree list, and the parent dir IFF this\n> dir is named .git, telling me of my mistake and offering me one or more worktrees to\n> switch to.\n>\n> But please, don't just switch to another directory without asking. This is just\n> encouraging me to make careless errors.\n>\n> Mark\n>\nI dug a bit more into the startup logic, and I think I better understand rework that is\nneeded.\n\nTwo basic problems I see here are beyond the question of if (and when) git-gui should try\nto locate a worktree:\n\n- git gui blame in a gitdir was apparently broken by the git repo commit 2d92ab32fd\n(\"rev-parse: make --show-toplevel without a worktree an error\", 2019-11-19). Prior to that\ncommit, git gui would stay in the startup directory enabling only features that cannot\nmodify the repository, and gitk could bring this view up in a gitdir. This doesn't work\nright now.\n\n- git-gui's logic includes a conceptual error embodied in proc is_bare: is_bare uses $(git\nrev-parse --is-bare-repository) but what we need is $(git rev-parse --is-inside-git-dir),\nand these are not synonyms.\nIt does not matter whether a worktree exists that points at the gitdir, and as discussed\nbefore, main worktrees can easily exist that we cannot locate from the gitdir. At best,\nis_bare is a guess.\nSo, is_bare should be replaced by is_inside_gitdir, and we should also have\nis_inside_worktree. These are mutually exclusive, though both can be false.\n\nMy current idea of an improved startup flow enables features only at the end:\n\n1) If not in a gitdir or worktree, \n    1a)   if git gui's subcommand is not 'gui', abort with an error (citool, browser, or\nblame invocations carry information specific to a gitdir/worktree). \n    1b)   otherwise, invoke repository_chooser, which either aborts, or changes directory\nto a worktree.\n\n-- we are now in a gitdir or a worktree, and this may or may not be the startup directory.\n\n2) Look at the combination of git gui subcommand and directory type (worktree / gitdir) to\ndecide to continue.\n    2a) blame / browser are ok in either directory type.\n    2b) citool requires a specific worktree, which should have been the initial startup\ndirectory. Abort if not.\n    2b) gui requires a worktree. Abort if not (my recommendation), or offer to find (or\nautomatically find) a worktree.\n\n3) Change directory to the top level of of the directory_type (git rev-parse knows\ntoplevel of a worktree, different code is needed for a gitdir).\n\n4) Enable features based upon subcommand and directory type.\n\nThere are 12 combinations of initial directory type (gitdir, worktree, neither) and\nsubcommand (gui, blame, browser, citool) to consider, with a lot of duplicated code\namongst the 12 cases. So, obviously, steps 1 and 2 can be convolved in many ways that are\ndifferent than what I wrote above.\n\nMark\n"},{"id":"542599","messageId":"73b99b54-1d39-45c1-bd06-26ac1008fddb@kdbg.org","threadId":"65531","inReplyTo":"93e1c61f-e58b-4a0c-8ece-7a8d945fa900@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-03T08:53:04Z","receivedAt":"2026-05-03T08:53:23Z","isPatch":true,"body":"Am 02.05.26 um 23:51 schrieb Mark Levedahl:\n> Two basic problems I see here are beyond the question of if (and when) git-gui should try\n> to locate a worktree:\n> \n> - git gui blame in a gitdir was apparently broken by the git repo commit 2d92ab32fd\n> (\"rev-parse: make --show-toplevel without a worktree an error\", 2019-11-19). Prior to that\n> commit, git gui would stay in the startup directory enabling only features that cannot\n> modify the repository, and gitk could bring this view up in a gitdir. This doesn't work\n> right now.\n\nTrue, `blame` used to work in bare repositories, but is broken now.\n\n> - git-gui's logic includes a conceptual error embodied in proc is_bare: is_bare uses $(git\n> rev-parse --is-bare-repository) but what we need is $(git rev-parse --is-inside-git-dir),\n> and these are not synonyms.\n> It does not matter whether a worktree exists that points at the gitdir, and as discussed\n> before, main worktrees can easily exist that we cannot locate from the gitdir. At best,\n> is_bare is a guess.\n> So, is_bare should be replaced by is_inside_gitdir, and we should also have\n> is_inside_worktree. These are mutually exclusive, though both can be false.\n\nI would not call the use of --is-bar-repository instead of\n--is-inside-git-dir an error, just a choice that has been made. In\nparticular, when the startup directory is named '.git' and is not marked\nas bare, then its parent directory can very reasonably be taken as its\nworktree. (That's how things worked before --show-toplevel was used.) If\nthe check is for --is-inside-git-dir, this treatment would be ruled out\nearly.\n\n\n> My current idea of an improved startup flow enables features only at the end:\n> \n> 1) If not in a gitdir or worktree, \n>     1a)   if git gui's subcommand is not 'gui', abort with an error (citool, browser, or\n> blame invocations carry information specific to a gitdir/worktree). \n>     1b)   otherwise, invoke repository_chooser, which either aborts, or changes directory\n> to a worktree.\n> \n> -- we are now in a gitdir or a worktree, and this may or may not be the startup directory.\n> \n> 2) Look at the combination of git gui subcommand and directory type (worktree / gitdir) to\n> decide to continue.\n>     2a) blame / browser are ok in either directory type.\n>     2b) citool requires a specific worktree, which should have been the initial startup\n> directory. Abort if not.\n>     2b) gui requires a worktree. Abort if not (my recommendation), or offer to find (or\n> automatically find) a worktree.\n> \n> 3) Change directory to the top level of of the directory_type (git rev-parse knows\n> toplevel of a worktree, different code is needed for a gitdir).\n> \n> 4) Enable features based upon subcommand and directory type.\n> \n> There are 12 combinations of initial directory type (gitdir, worktree, neither) and\n> subcommand (gui, blame, browser, citool) to consider, with a lot of duplicated code\n> amongst the 12 cases. So, obviously, steps 1 and 2 can be convolved in many ways that are\n> different than what I wrote above.\nSo true.\n\nBut perhaps there is a simpler solution: Let's present an error if\n--show-toplevel fails except in the case where the startup directory is\nnamed '.git' (and is a valid Git repository) and is not bare (then the\nworktree is the parent). I insist in this exception, because this\nuse-case was considered important in the past (87cd09f43e56 \"git-gui:\nwork from the .git dir\", 2010-01-23).\n\n-- Hannes\n\n"},{"id":"542678","messageId":"tencent_78B80FB7A0A42E464B3EF1841E2AF3C39509@qq.com","threadId":"65531","inReplyTo":"tencent_277823B7C5D69914E168E5679A907C655606@qq.com","subject":"[PATCH v5 1/1] git-gui: restructure repository startup","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-04T14:59:19Z","receivedAt":"2026-05-04T15:00:05Z","isPatch":true,"body":"When git-gui is started inside a .git directory of a non-bare\nrepository, it should treat the parent directory as the worktree,\nas it did before commit 2d92ab32fd (rev-parse: make --show-toplevel\nwithout a worktree an error, 2019-11-19).  However, a bare repository\nor a separated gitdir without a worktree must be rejected early.\n\nProtect the previously unguarded calls to `git rev-parse\n--show-object-format` and `--show-toplevel`.  Restructure the startup\nsequence to:\n\n- Check for a bare repository right after loading the config.  If the\n  repository is bare and the current subcommand does not allow bare\n  repos (e.g. normal commit mode), show \"Cannot use bare repository\"\n  and exit.\n\n- When `rev-parse --show-toplevel` fails and the repository is\n  non-bare, the gitdir path ends with \".git\", and we are inside that\n  gitdir, use the parent directory as the worktree.  This preserves\n  the ability to start git-gui from within a regular repository’s\n  .git directory, which was intentionally supported since 87cd09f43e56\n  (git-gui: work from the .git dir, 2010-01-23).\n\n- Otherwise, show a descriptive error and exit.\n\n- Wrap `rev-parse --show-object-format` in a catch to avoid a crash\n  when the repository configuration is broken (e.g. core.worktree\n  pointing to an invalid path).\n\nAlso removes the old `_prefix`‑based fallback that computed a relative\npath to the worktree top from a subdirectory, and the unconditional\n`[file dirname $_gitdir]` guess.  Both are unnecessary now that\n`rev‑parse --show‑toplevel` directly provides the absolute top‑level\npath and we can `cd` to it.  The guess is further unsafe in\nmulti‑worktree setups, where a gitdir may have more than one worktree.\nThe only remaining fallback is the explicit “.git directory” rule for\nnon‑bare repositories, which mirrors the historical behaviour.\n\nThis fixes the fatal Tcl error when the working tree is missing, while\nkeeping the .git startup feature and avoiding any automatic directory\nswitching that could be dangerous in multi‑worktree setups.\n\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 72 +++++++++++++++++++++++++++++-----------------\n 1 file changed, 45 insertions(+), 27 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..c06d85b8d9 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1129,7 +1129,8 @@ if {[catch {\n \t\t}]\n \t&& [catch {\n \t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n+\t\t# and _prefix to the empty string; this is handled by\n+\t\t# the startup safety checks below\n \t\tset _gitdir [git rev-parse --git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n@@ -1142,8 +1143,20 @@ if {[catch {\n \tset picked 1\n }\n \n+if {![file isdirectory $_gitdir]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat \n+\t\t[mc \"Git directory not found:\"] \"\\n\\n$_gitdir\\n\\n\" \\\n+\t\t[mc \"Please ensure GIT_DIR points to a valid Git repository\"]]\n+\texit 1\n+}\n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n-set hashalgorithm [git rev-parse --show-object-format]\n+if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Failed to determine hash algorithm:\"] \"\\n\\n$err\"]\n+\texit 1\n+}\n if {$hashalgorithm eq \"sha1\"} {\n \tset hashlength 40\n } elseif {$hashalgorithm eq \"sha256\"} {\n@@ -1160,46 +1173,50 @@ if {$_gitdir eq \".\"} {\n \tset _gitdir [pwd]\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n+# Handle bare repository early: if not allowed, abort\n+if {[is_bare] && ![is_enabled bare]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" [file normalize $_gitdir]]\n+\texit 1\n+}\n \n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n-\t\texit 1\n+# Determine the working tree\n+if {[is_bare] && [is_enabled bare]} {\n+\tset _gitworktree {}\n+} else {\n+\tif {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {\n+\t\t# If we are inside a .git directory of a non-bare repo,\n+\t\t# the worktree is the parent directory\n+\t\tset inside_gitdir 0\n+\t\tcatch {set inside_gitdir [git rev-parse --is-inside-git-dir]}\n+\t\tif {![is_bare] && $inside_gitdir eq {true} && [file tail [file normalize $_gitdir]] eq {.git}} {\n+\t\t\tset _gitworktree [file normalize [file dirname $_gitdir]]\n+\t\t} else {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Cannot determine working tree:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n \t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n+\n+\tif {$_gitworktree eq {}} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n+\t\terror_popup [mc \"Cannot determine working tree (unexpected empty result)\"]\n \t\texit 1\n \t}\n-\tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n-\t}\n+\n \tif {[catch {cd $_gitworktree} err]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Cannot move to working directory:\"] \"\\n\\n$err\"]\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n }\n+\n+# Derive a human-readable repository name\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1207,6 +1224,7 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n+# Export the final paths\n set env(GIT_DIR) $_gitdir\n set env(GIT_WORK_TREE) $_gitworktree\n \n-- \n2.52.0.windows.1\n\n"},{"id":"542679","messageId":"7d5cf952-badb-4071-a0eb-af9443fa8b5b@gmail.com","threadId":"65531","inReplyTo":"73b99b54-1d39-45c1-bd06-26ac1008fddb@kdbg.org","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-04T15:13:33Z","receivedAt":"2026-05-04T15:13:35Z","isPatch":true,"body":"\n\nOn 5/3/26 4:53 AM, Johannes Sixt wrote:\n> I would not call the use of --is-bar-repository instead of\n> --is-inside-git-dir an error, just a choice that has been made. In\n> particular, when the startup directory is named '.git' and is not marked\n> as bare, then its parent directory can very reasonably be taken as its\n> worktree. (That's how things worked before --show-toplevel was used.) If\n> the check is for --is-inside-git-dir, this treatment would be ruled out\n> early.\n>\nWhether being in a gitdir is ok, or a worktree required, is of fundamental importance and\nis not explicitly checked now. This is my issue. (Whether the repo is bare, or embedded in\na worktree, is relevant only when automatically fixing a user error.)\n> But perhaps there is a simpler solution: Let's present an error if\n> --show-toplevel fails except in the case where the startup directory is\n> named '.git' (and is a valid Git repository) and is not bare (then the\n> worktree is the parent). I insist in this exception, because this\n> use-case was considered important in the past (87cd09f43e56 \"git-gui:\n> work from the .git dir\", 2010-01-23).\n>\n> -- Hannes\n>\n\nThis would not fix gitk's blame / browse from a gitdir, and I don't really see a one or\ntwo line fix as being adequate.\n\ngit-gui sets GIT_WORK_TREE and GIT_DIR at startup. GIT_DIR passes my simple tests, but\nmishandles GIT_WORK_TREE.\n\nI expect these two invocations to be equivalent, both starting git-gui in the worktree\n'/some/path':\n\n    GIT_WORK_TREE=/some/path git gui\n    git -C /some/path gui\n\nBut, the GIT_WORK_TREE approach:\n    works as I expect ONLY when the current directory is a valid worktree\n    when started from a gitdir, uses that gitdir in conjunction with the requested worktree\n    when started from an uncontrolled directory, shows the repository picker.\n\nThe git -C approach is indifferent to the current directory, of course.\n\nGIT_WORK_TREE enters much too late in the process, and rather should handled first:\n    if GIT_WORK_TREE is in the environment, cd to that first. Throw an error if that\ndirectory is not a valid worktree.\n\nI don't actually understand the use case of defining GIT_DIR or GIT_WORK_TREE to git gui,\nand I wonder what other bugs are lurking... maybe the better approach is to just abort if\nGIT_DIR or GIT_WORK_TREE are defined?\n\nMark\n"},{"id":"542738","messageId":"f01d89c0-12ae-43b0-84a5-9e4132f584f8@gmail.com","threadId":"65531","inReplyTo":"7d5cf952-badb-4071-a0eb-af9443fa8b5b@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-05T03:40:50Z","receivedAt":"2026-05-05T03:40:51Z","isPatch":true,"body":">\n> On 5/3/26 4:53 AM, Johannes Sixt wrote:\n>> But perhaps there is a simpler solution: Let's present an error if\n>> --show-toplevel fails except in the case where the startup directory is\n>> named '.git' (and is a valid Git repository) and is not bare (then the\n>> worktree is the parent). I insist in this exception, because this\n>> use-case was considered important in the past (87cd09f43e56 \"git-gui:\n>> work from the .git dir\", 2010-01-23).\n>>\n>> -- Hannes\n>>\n\nI've restructured startup code in line with what I suggested before, allowing operation in\na worktree or a gitdir, and with various combinations of GIT_DIR and GIT_WORK_TREE\nenvironment variables set. Unfortunately, git-gui's blame and browser commands simply do\nnot now work without a valid worktree. The error(s) are not obvious to me, and bisecting\nrequires git version 2.24 or earlier to remove 2d92ab32fd (\"rev-parse: make\n--show-toplevel without a worktree an error\", 2019-11-19) to even start: many gcc and\ngit/git-gui version compatibility issues are certain to arise. I won't be doing this.\n\nSo, I believe your suggestion above is the best path, leaving behind dead code that\npurports to support operation from a gitdir but does not.\n\nMark\n\n\n"},{"id":"542800","messageId":"28db8297-c995-4e29-ae44-ade304b2aad5@kdbg.org","threadId":"65531","inReplyTo":"tencent_78B80FB7A0A42E464B3EF1841E2AF3C39509@qq.com","subject":"Re: [PATCH v5 1/1] git-gui: restructure repository startup","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-06T07:15:33Z","receivedAt":"2026-05-06T07:15:43Z","isPatch":true,"body":"Am 04.05.26 um 16:59 schrieb Shroom Moo:\n> When git-gui is started inside a .git directory of a non-bare\n> repository, it should treat the parent directory as the worktree,\n> as it did before commit 2d92ab32fd (rev-parse: make --show-toplevel\n> without a worktree an error, 2019-11-19).  However, a bare repository\n> or a separated gitdir without a worktree must be rejected early.\n> \n> Protect the previously unguarded calls to `git rev-parse\n> --show-object-format` and `--show-toplevel`.  Restructure the startup\n> sequence to:\n> \n> - Check for a bare repository right after loading the config.  If the\n>   repository is bare and the current subcommand does not allow bare\n>   repos (e.g. normal commit mode), show \"Cannot use bare repository\"\n>   and exit.\n> \n> - When `rev-parse --show-toplevel` fails and the repository is\n>   non-bare, the gitdir path ends with \".git\", and we are inside that\n>   gitdir, use the parent directory as the worktree.  This preserves\n>   the ability to start git-gui from within a regular repository’s\n>   .git directory, which was intentionally supported since 87cd09f43e56\n>   (git-gui: work from the .git dir, 2010-01-23).\n> \n> - Otherwise, show a descriptive error and exit.\n\nVery good. This does things in the right order, IMO.\n\n> \n> - Wrap `rev-parse --show-object-format` in a catch to avoid a crash\n>   when the repository configuration is broken (e.g. core.worktree\n>   pointing to an invalid path).\n\nNice catch. This could be moved into its own patch. But it is acceptable\nin this patch as it loosely fits the topic.\n\n> \n> Also removes the old `_prefix`‑based fallback that computed a relative\n> path to the worktree top from a subdirectory, and the unconditional\n> `[file dirname $_gitdir]` guess.  Both are unnecessary now that\n> `rev‑parse --show‑toplevel` directly provides the absolute top‑level\n> path and we can `cd` to it.  The guess is further unsafe in\n> multi‑worktree setups, where a gitdir may have more than one worktree.\n> The only remaining fallback is the explicit “.git directory” rule for\n> non‑bare repositories, which mirrors the historical behaviour.\n\nNice cleanup.\n\n> \n> This fixes the fatal Tcl error when the working tree is missing, while\n> keeping the .git startup feature and avoiding any automatic directory\n> switching that could be dangerous in multi‑worktree setups.\n\nGood!\n\nHowever, this doesn't fix `git gui blame HEAD file` in a bare\nrepository, because `git branch --show-current` fails with an empty\nGIT_WORK_TREE value. Fixing this needs to consider whether to set\nGIT_DIR and GIT_WORK_TREE at all, as alluded to by Mark in a near-by\nmessage. This is a separate topic.\n\n> \n> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n> ---\n>  git-gui/git-gui.sh | 72 +++++++++++++++++++++++++++++-----------------\n>  1 file changed, 45 insertions(+), 27 deletions(-)\n> \n> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n> index 23fe76e498..c06d85b8d9 100755\n> --- a/git-gui/git-gui.sh\n> +++ b/git-gui/git-gui.sh\n> @@ -1129,7 +1129,8 @@ if {[catch {\n>  \t\t}]\n>  \t&& [catch {\n>  \t\t# beware that from the .git dir this sets _gitdir to .\n> -\t\t# and _prefix to the empty string\n> +\t\t# and _prefix to the empty string; this is handled by\n> +\t\t# the startup safety checks below\n>  \t\tset _gitdir [git rev-parse --git-dir]\n>  \t\tset _prefix [git rev-parse --show-prefix]\n>  \t} err]} {\n> @@ -1142,8 +1143,20 @@ if {[catch {\n>  \tset picked 1\n>  }\n>  \n> +if {![file isdirectory $_gitdir]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat \n> +\t\t[mc \"Git directory not found:\"] \"\\n\\n$_gitdir\\n\\n\" \\\n> +\t\t[mc \"Please ensure GIT_DIR points to a valid Git repository\"]]\n> +\texit 1\n> +}\n> +\n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n> -set hashalgorithm [git rev-parse --show-object-format]\n> +if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat [mc \"Failed to determine hash algorithm:\"] \"\\n\\n$err\"]\n> +\texit 1\n> +}\n>  if {$hashalgorithm eq \"sha1\"} {\n>  \tset hashlength 40\n>  } elseif {$hashalgorithm eq \"sha256\"} {\n> @@ -1160,46 +1173,50 @@ if {$_gitdir eq \".\"} {\n>  \tset _gitdir [pwd]\n>  }\n>  \n> -if {![file isdirectory $_gitdir]} {\n> -\tcatch {wm withdraw .}\n> -\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n> -\texit 1\n> -}\n>  # _gitdir exists, so try loading the config\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n> +# Handle bare repository early: if not allowed, abort\n> +if {[is_bare] && ![is_enabled bare]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" [file normalize $_gitdir]]\n> +\texit 1\n> +}\n>  \n> -if {$_prefix ne {}} {\n> -\tif {$_gitworktree eq {}} {\n> -\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n> -\t} else {\n> -\t\tset cdup $_gitworktree\n> -\t}\n> -\tif {[catch {cd $cdup} err]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n> -\t\texit 1\n> +# Determine the working tree\n> +if {[is_bare] && [is_enabled bare]} {\n> +\tset _gitworktree {}\n\nI strongly suggest to collapse this branch and the previous 'if\n{[is_bare] && ![is_enabled bare]}' into a single 'if {[is_bare]}',\nbecause then in the else-branch below we can be sure not to have a bare\nrepository.\n\n> +} else {\n> +\tif {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {\n> +\t\t# If we are inside a .git directory of a non-bare repo,\n> +\t\t# the worktree is the parent directory\n> +\t\tset inside_gitdir 0\n> +\t\tcatch {set inside_gitdir [git rev-parse --is-inside-git-dir]}\n> +\t\tif {![is_bare] && $inside_gitdir eq {true} && [file tail [file normalize $_gitdir]] eq {.git}} {\n\nDo we need to 'file normalize' before taking the 'file tail'? If not,\nthen the line would be shorter.\n\n> +\t\t\tset _gitworktree [file normalize [file dirname $_gitdir]]\n> +\t\t} else {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [strcat [mc \"Cannot determine working tree:\"] \"\\n\\n$err\"]\n> +\t\t\texit 1\n> +\t\t}\n>  \t}\n> -\tset _gitworktree [pwd]\n> -\tunset cdup\n> -} elseif {![is_enabled bare]} {\n> -\tif {[is_bare]} {\n> +\n> +\tif {$_gitworktree eq {}} {\n>  \t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n> +\t\terror_popup [mc \"Cannot determine working tree (unexpected empty result)\"]\n>  \t\texit 1\n>  \t}\n> -\tif {$_gitworktree eq {}} {\n> -\t\tset _gitworktree [file dirname $_gitdir]\n> -\t}\n> +\n>  \tif {[catch {cd $_gitworktree} err]} {\n>  \t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n> +\t\terror_popup [strcat [mc \"Cannot move to working directory:\"] \"\\n\\n$err\"]\n>  \t\texit 1\n>  \t}\n>  \tset _gitworktree [pwd]\n>  }\n> +\n> +# Derive a human-readable repository name\n>  set _reponame [file split [file normalize $_gitdir]]\n>  if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end-1]\n> @@ -1207,6 +1224,7 @@ if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end]\n>  }\n>  \n> +# Export the final paths\n>  set env(GIT_DIR) $_gitdir\n>  set env(GIT_WORK_TREE) $_gitworktree\n>  \n\n-- Hannes\n\n"},{"id":"542801","messageId":"ac115a8f-5dbc-4988-b8a5-c1647af1bb74@kdbg.org","threadId":"65531","inReplyTo":"7d5cf952-badb-4071-a0eb-af9443fa8b5b@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-06T07:32:02Z","receivedAt":"2026-05-06T07:32:07Z","isPatch":true,"body":"Am 04.05.26 um 17:13 schrieb Mark Levedahl:\n> On 5/3/26 4:53 AM, Johannes Sixt wrote:\n>> I would not call the use of --is-bar-repository instead of\n>> --is-inside-git-dir an error, just a choice that has been made. In\n>> particular, when the startup directory is named '.git' and is not marked\n>> as bare, then its parent directory can very reasonably be taken as its\n>> worktree. (That's how things worked before --show-toplevel was used.) If\n>> the check is for --is-inside-git-dir, this treatment would be ruled out\n>> early.\n>>\n> Whether being in a gitdir is ok, or a worktree required, is of fundamental importance and\n> is not explicitly checked now. This is my issue. (Whether the repo is bare, or embedded in\n> a worktree, is relevant only when automatically fixing a user error.)\n\nI don't quite follow what you a trying to say here.\n\n>> But perhaps there is a simpler solution: Let's present an error if\n>> --show-toplevel fails except in the case where the startup directory is\n>> named '.git' (and is a valid Git repository) and is not bare (then the\n>> worktree is the parent). I insist in this exception, because this\n>> use-case was considered important in the past (87cd09f43e56 \"git-gui:\n>> work from the .git dir\", 2010-01-23).\n>>\n>> -- Hannes\n>>\n> \n> This would not fix gitk's blame / browse from a gitdir, and I don't really see a one or\n> two line fix as being adequate.\n> \n> git-gui sets GIT_WORK_TREE and GIT_DIR at startup. GIT_DIR passes my simple tests, but\n> mishandles GIT_WORK_TREE.\n> \n> I expect these two invocations to be equivalent, both starting git-gui in the worktree\n> '/some/path':\n> \n>     GIT_WORK_TREE=/some/path git gui\n>     git -C /some/path gui\n> \n> But, the GIT_WORK_TREE approach:\n>     works as I expect ONLY when the current directory is a valid worktree\n>     when started from a gitdir, uses that gitdir in conjunction with the requested worktree\n>     when started from an uncontrolled directory, shows the repository picker.\n\nThe important aspect here isn't about the worktree, but whether a gitdir\ncan be determined for the current directory. All three observations make\ntotal sense.\n\nThat said, setting GIT_WORK_TREE without also setting GIT_DIR is\nundefined and need not be considered further.\n\n> The git -C approach is indifferent to the current directory, of course.\n> \n> GIT_WORK_TREE enters much too late in the process, and rather should handled first:\n>     if GIT_WORK_TREE is in the environment, cd to that first. Throw an error if that\n> directory is not a valid worktree.\n\nAs I said, GIT_WORK_TREE without GIT_DIR is an invalid use-case. For\nthis reason, the first thing to do is find the database, and from there\nwork out the worktree. In the most common use-case it is the current\ndirectory.\n\n> I don't actually understand the use case of defining GIT_DIR or GIT_WORK_TREE to git gui,\n> and I wonder what other bugs are lurking... maybe the better approach is to just abort if\n> GIT_DIR or GIT_WORK_TREE are defined?\nI lean towards setting GIT_DIR always. This is necessary, because Git\nGUI can be run from a subdirectory of the worktree, and then changes\ndirectory to the top-level. It must be ensured that the same GIT_DIR is\nused that was detected from the subdirectory.\n\nNow that the current directory is at the top-level of the worktree, we\ncould just not set GIT_WORK_TREE at all, provided that setting GIT_DIR\nwithout GIT_WORK_TREE is a valid use-case for Git. I am not yet sure\nabout that.\n\n-- Hannes\n\n"},{"id":"542806","messageId":"f6c7c3d5-1d68-45b5-87a7-ae19b59270f4@gmail.com","threadId":"65531","inReplyTo":"ac115a8f-5dbc-4988-b8a5-c1647af1bb74@kdbg.org","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-06T11:27:59Z","receivedAt":"2026-05-06T11:28:01Z","isPatch":true,"body":"\n\nOn 5/6/26 3:32 AM, Johannes Sixt wrote:\n>> Whether being in a gitdir is ok, or a worktree required, is of fundamental importance and\n>> is not explicitly checked now. This is my issue. (Whether the repo is bare, or embedded in\n>> a worktree, is relevant only when automatically fixing a user error.)\n> I don't quite follow what you a trying to say here.\nI played a bit more:\n\nA git repository (gitdir) can have config.bare true | false | not set\ngit rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the\ncurrent directory has core.bare==true. This happens whether the call is from inside the\ngitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a\nsymlink or a gitfile link to the gitdir. This call never tells you what directory you are\nactually in.\n\ngit rev-parse --is-inside-work-tree gives:\n    true - the call is made from a directory that is suported/supportable as a worktree of\na gitdir.\n    false - the call is made from inside a gitdir, or from a directory linked to a to a\ngitdir with core.bare == true.\n    and error is thrown if no gitdir is discovered.\n\nI find --is-inside-work-tree a much better call to make early in setup. \n    true - full git-gui is ok, \n    false - blame/browser is ok (gitdir might have core.bare true)\n    error - no gitdir found, the repository picker should be called.\n\nSo, the only need to test if the repo is marked bare is when looking for a possible\nworktree when git-gui was started inside the gitdir, or started in a directory linked to\nsaid gitdir, or GIT_DIR in the environment points to said gitdir: I consider all of this a\nuser (or configuration) error, and there are many possible causes to explore to give\nuseful feedback to the user.\n\nBut, there are many ways to code this. I started down a path of using\n--is-inside-worktree, but in the end there are still a lot of corner cases to find.\n\n>>> But perhaps there is a simpler solution: Let's present an error if\n>>> --show-toplevel fails except in the case where the startup directory is\n>>> named '.git' (and is a valid Git repository) and is not bare (then the\n>>> worktree is the parent). I insist in this exception, because this\n>>> use-case was considered important in the past (87cd09f43e56 \"git-gui:\n>>> work from the .git dir\", 2010-01-23).\n>>>\n>>> -- Hannes\n>>>\n>> This would not fix gitk's blame / browse from a gitdir, and I don't really see a one or\n>> two line fix as being adequate.\n\nAs you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty\nwhen no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty\nvariable. This cause is in a commit from 12 years ago:\n\n    3decb8e0ac (\"git-gui: tolerate major version changes when comparing the git version\",\n2014-05-17)\n\nPrior to that commit and if not using git v1.7.x, an alternate branch of code not using\ngit rev-parse was used for worktree discovery, and that code set _gitworktree = _gitdir\nwhen in a gitdir. The alternate code was removed more recently as it was unreachable from\nnon-ancient git versions.\n\nThe fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not\nexport an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for\ntrouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My\nlimited testing shows this works.\n\n\nMark\n\n"},{"id":"542809","messageId":"9fecbb11-3cc5-4084-bc29-bd948962dca0@kdbg.org","threadId":"65531","inReplyTo":"f6c7c3d5-1d68-45b5-87a7-ae19b59270f4@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-06T12:57:15Z","receivedAt":"2026-05-06T12:57:22Z","isPatch":true,"body":"Am 06.05.26 um 13:27 schrieb Mark Levedahl:\n> A git repository (gitdir) can have config.bare true | false | not set\n> git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the\n> current directory has core.bare==true. This happens whether the call is from inside the\n> gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a\n> symlink or a gitfile link to the gitdir. This call never tells you what directory you are\n> actually in.\n\nOK. But how does \"find out which directory we are in\" come into play\nhere? If we find a bare repository, we do not need a worktree. If we are\nin a non-bare repository, we can find the worktree with `rev-parse\n--show-toplevel`.\n\n> \n> git rev-parse --is-inside-work-tree gives:\n>     true - the call is made from a directory that is suported/supportable as a worktree of\n> a gitdir.\n>     false - the call is made from inside a gitdir, or from a directory linked to a to a\n> gitdir with core.bare == true.\n>     and error is thrown if no gitdir is discovered.\n> \n> I find --is-inside-work-tree a much better call to make early in setup. \n>     true - full git-gui is ok, \n>     false - blame/browser is ok (gitdir might have core.bare true)\n>     error - no gitdir found, the repository picker should be called.\n\nBut we would still make an exception for the case that $PWD is a\nnon-bare repository named \".git\", because then, by Git GUI's definition,\nits parent is the corresponding worktree.\n\n> So, the only need to test if the repo is marked bare is when looking for a possible\n> worktree when git-gui was started inside the gitdir, or started in a directory linked to\n> said gitdir, or GIT_DIR in the environment points to said gitdir: I consider all of this a\n> user (or configuration) error, and there are many possible causes to explore to give\n> useful feedback to the user.\n\nHow does this scheme work when the user starts `git gui blame` in a bare\nrepository that does not have a worktree? Would this not produce an\nerror because no worktree was found?\n\n> As you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty\n> when no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty\n> variable. This cause is in a commit from 12 years ago:\n> \n>     3decb8e0ac (\"git-gui: tolerate major version changes when comparing the git version\",\n> 2014-05-17)\n\nI don't think that this commit very relevant. The problem is in `git\nbranch --show-current` (and probably other git command variants) that\nwant to turn an empty $GIT_WORK_TREE into an absolute path even in cases\nwhere no worktree is needed. I haven't tried to figure out which commit\n(in the Git repository) started to do this.\n\n> The fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not\n> export an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for\n> trouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My\n> limited testing shows this works.\nGood to know. My preference is to not set GIT_WORK_TREE at all provided\nthat setting GIT_DIR without GIT_WORK_TREE is a use-case supported by Git.\n\n-- Hannes\n\n"},{"id":"542811","messageId":"7a963eec-80d2-4605-8cb1-52fb7bc9cf8e@gmail.com","threadId":"65531","inReplyTo":"9fecbb11-3cc5-4084-bc29-bd948962dca0@kdbg.org","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-06T14:05:04Z","receivedAt":"2026-05-06T14:05:08Z","isPatch":true,"body":"\n\nOn 5/6/26 8:57 AM, Johannes Sixt wrote:\n> Am 06.05.26 um 13:27 schrieb Mark Levedahl:\n>> A git repository (gitdir) can have config.bare true | false | not set\n>> git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the\n>> current directory has core.bare==true. This happens whether the call is from inside the\n>> gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a\n>> symlink or a gitfile link to the gitdir. This call never tells you what directory you are\n>> actually in.\n> OK. But how does \"find out which directory we are in\" come into play\n> here? If we find a bare repository, we do not need a worktree. If we are\n> in a non-bare repository, we can find the worktree with `rev-parse\n> --show-toplevel`.\n>\n>> git rev-parse --is-inside-work-tree gives:\n>>     true - the call is made from a directory that is suported/supportable as a worktree of\n>> a gitdir.\n>>     false - the call is made from inside a gitdir, or from a directory linked to a to a\n>> gitdir with core.bare == true.\n>>     and error is thrown if no gitdir is discovered.\n>>\n>> I find --is-inside-work-tree a much better call to make early in setup. \n>>     true - full git-gui is ok, \n>>     false - blame/browser is ok (gitdir might have core.bare true)\n>>     error - no gitdir found, the repository picker should be called.\n> But we would still make an exception for the case that $PWD is a\n> non-bare repository named \".git\", because then, by Git GUI's definition,\n> its parent is the corresponding worktree.\n\nI find the organization using rev-parse --is-inside-work-tree easier to reason about, and\nif I were writing this from scratch, I would do it that way. But, you have one or more\npatches in progress, if this idea is useful there great, otherwise, drop it.\n\n>> As you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty\n>> when no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty\n>> variable. This cause is in a commit from 12 years ago:\n>>\n>>     3decb8e0ac (\"git-gui: tolerate major version changes when comparing the git version\",\n>> 2014-05-17)\n> I don't think that this commit very relevant. The problem is in `git\n> branch --show-current` (and probably other git command variants) that\n> want to turn an empty $GIT_WORK_TREE into an absolute path even in cases\n> where no worktree is needed. I haven't tried to figure out which commit\n> (in the Git repository) started to do this.\n\nReverting that commit in any way has nothing to do with fixing this problem now. But,\ndetecting breakage at that commit is what lead me to discover the problem was _gitworktree\n== {} and GIT_WORK_TREE=\"\". As you say, browser/blame may well not have broken until a\nmore recent commit to git itself. I cannot say, even the 2019 git prior to rev parse\n--top-level being taught to error out will not build on my computer due to incompatibilities.\n\n>> The fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not\n>> export an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for\n>> trouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My\n>> limited testing shows this works.\n> Good to know. My preference is to not set GIT_WORK_TREE at all provided\n> that setting GIT_DIR without GIT_WORK_TREE is a use-case supported by Git.\n>\n>\n\nI just confirmed that git-gui modified to export GIT_DIR only, not GIT_WORK_TREE, and\nactually to make sure GIT_WORK_TREE is not in env, has blame/browser working correctly in\na gitdir with no worktree.\n\nMark\n"},{"id":"542815","messageId":"tencent_DDD6467B3F6184562B51C23BB9DBB79EA409@qq.com","threadId":"65531","inReplyTo":"tencent_78B80FB7A0A42E464B3EF1841E2AF3C39509@qq.com","subject":"[PATCH v6 0/3] git-gui: robustify startup and fix environment handling","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-06T20:27:48Z","receivedAt":"2026-05-06T20:29:04Z","isPatch":true,"body":"This series addresses the startup crash introduced by Git commit\n\"2d92ab32fd (\"rev-parse: make --show-toplevel without a worktree an\nerror\", 2019-11-19)\", which causes `git gui` to die with a Tcl error\nwhen a worktree is missing (e.g. inside a .git directory without a\nworking tree, or in a bare repository).\n\nAdditionally, it resolves two historically inconsistent behaviours:\n- The \"Visualize ... History\" menu items were enabled in bare\n  repositories but triggered Tcl errors due to the missing worktree.\n- `GIT_WORK_TREE` and `GIT_DIR` environment variables were not\n  respected early enough, so `GIT_WORK_TREE=/some/path git gui`\n  often ignored the explicit worktree and brought up the repository\n  picker, and an exported empty `GIT_WORK_TREE` confused commands\n  like `git branch --show-current` in bare repositories.\n\nShroom Moo (3):\n  git-gui: restructure repository startup\n  git-gui: disable gitk visualization when no worktree available\n  git-gui: handle GIT_DIR and GIT_WORK_TREE early\n\n git-gui/git-gui.sh | 170 ++++++++++++++++++++++++++++++++++-----------\n 1 file changed, 128 insertions(+), 42 deletions(-)\n\n-- \n2.52.0.windows.1\n\n"},{"id":"542816","messageId":"tencent_02420881568A85BB718B6E7949E18FDE7D0A@qq.com","threadId":"65531","inReplyTo":"20260506202751.3294-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v6 2/3] git-gui: disable gitk visualization when no worktree available","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-06T20:27:50Z","receivedAt":"2026-05-06T20:29:05Z","isPatch":true,"body":"When git-gui is started in a bare repository with the 'bare' option\nenabled (e.g., for blame/browser), there is no working tree.  The\n\"Visualize Current Branch's History\" and \"Visualize All Branch\nHistory\" menu items remain enabled, but clicking them triggers a Tcl\nerror because do_gitk tries to change directory to an empty\n_gitworktree.\n\nFix this by disabling the two visualization menu items when the\nrepository is bare and the 'bare' option is active.  Also update\ncurrent_branch_write to keep the state consistent when the branch\nchanges, and add a defensive check in do_gitk to avoid the error\nshould the menu state somehow become out of sync.\n\nThis complements the startup sequence improvements in the previous\ncommit, which already correctly identifies bare repositories and\nleaves _gitworktree empty in such cases.\n\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex fbdc0b2a41..1191e6654c 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -2034,6 +2034,10 @@ proc do_gitk {revs {is_submodule false}} {\n \t#\n \tset exe [_which gitk -script]\n \tset cmd [list [info nameofexecutable] $exe]\n+\tif {$_gitworktree eq {}} {\n+\t\terror_popup [mc \"Cannot visualize history: no working tree\"]\n+\t\treturn\n+\t}\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find gitk in PATH\"]\n \t} else {\n@@ -2657,6 +2661,13 @@ set ui_visualize_current [.mbar.repository index last]\n .mbar.repository add command \\\n \t-label [mc \"Visualize All Branch History\"] \\\n \t-command {do_gitk --all}\n+set ui_visualize_all [.mbar.repository index last]\n+\n+# Cannot work without a working tree\n+if {[is_bare] && [is_enabled bare]} {\n+\t.mbar.repository entryconf $ui_visualize_current -state disabled\n+\t.mbar.repository entryconf $ui_visualize_all   -state disabled\n+}\n .mbar.repository add separator\n \n proc current_branch_write {args} {\n@@ -2665,6 +2676,13 @@ proc current_branch_write {args} {\n \t\t-label [mc \"Browse %s's Files\" $current_branch]\n \t.mbar.repository entryconf $::ui_visualize_current \\\n \t\t-label [mc \"Visualize %s's History\" $current_branch]\n+\tif {[is_bare] && [is_enabled bare]} {\n+\t\t.mbar.repository entryconf $::ui_visualize_current -state disabled\n+\t\t.mbar.repository entryconf $::ui_visualize_all   -state disabled\n+\t} else {\n+\t\t.mbar.repository entryconf $::ui_visualize_current -state normal\n+\t\t.mbar.repository entryconf $::ui_visualize_all   -state normal\n+\t}\n }\n trace add variable current_branch write current_branch_write\n \n-- \n2.52.0.windows.1\n\n\n"},{"id":"542817","messageId":"tencent_E834EEF92D17298D51E74F26219DCA807007@qq.com","threadId":"65531","inReplyTo":"20260506202751.3294-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v6 1/3] git-gui: restructure repository startup","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-06T20:27:49Z","receivedAt":"2026-05-06T20:29:08Z","isPatch":true,"body":"When git-gui is started inside a .git directory of a non-bare\nrepository, it should treat the parent directory as the worktree,\nas it did before commit 2d92ab32fd (rev-parse: make --show-toplevel\nwithout a worktree an error, 2019-11-19).  However, a bare repository\nor a separated gitdir without a worktree must be rejected early.\n\nProtect the previously unguarded calls to `git rev-parse\n--show-object-format` and `--show-toplevel`.  Restructure the startup\nsequence to:\n\n- Check for a bare repository right after loading the config.  If the\n  repository is bare and the current subcommand does not allow bare\n  repos (e.g. normal commit mode), show \"Cannot use bare repository\"\n  and exit.\n\n- When `rev-parse --show-toplevel` fails and the repository is\n  non-bare, the gitdir path ends with \".git\", and we are inside that\n  gitdir, use the parent directory as the worktree.  This preserves\n  the ability to start git-gui from within a regular repository’s\n  .git directory, which was intentionally supported since 87cd09f43e56\n  (git-gui: work from the .git dir, 2010-01-23).\n\n- Otherwise, show a descriptive error and exit.\n\n- Wrap `rev-parse --show-object-format` in a catch to avoid a crash\n  when the repository configuration is broken (e.g. core.worktree\n  pointing to an invalid path).\n\nAlso removes the old `_prefix`‑based fallback that computed a relative\npath to the worktree top from a subdirectory, and the unconditional\n`[file dirname $_gitdir]` guess.  Both are unnecessary now that\n`rev‑parse --show‑toplevel` directly provides the absolute top‑level\npath and we can `cd` to it.  The guess is further unsafe in\nmulti‑worktree setups, where a gitdir may have more than one worktree.\nThe only remaining fallback is the explicit “.git directory” rule for\nnon‑bare repositories, which mirrors the historical behaviour.\n\nThis fixes the fatal Tcl error when the working tree is missing, while\nkeeping the .git startup feature and avoiding any automatic directory\nswitching that could be dangerous in multi‑worktree setups.\n\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 72 +++++++++++++++++++++++++++++-----------------\n 1 file changed, 46 insertions(+), 26 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..fbdc0b2a41 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1129,7 +1129,8 @@ if {[catch {\n \t\t}]\n \t&& [catch {\n \t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n+\t\t# and _prefix to the empty string; this is handled by\n+\t\t# the startup safety checks below\n \t\tset _gitdir [git rev-parse --git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n@@ -1142,8 +1143,20 @@ if {[catch {\n \tset picked 1\n }\n \n+if {![file isdirectory $_gitdir]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat \n+\t\t[mc \"Git directory not found:\"] \"\\n\\n$_gitdir\\n\\n\" \\\n+\t\t[mc \"Please ensure GIT_DIR points to a valid Git repository\"]]\n+\texit 1\n+}\n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n-set hashalgorithm [git rev-parse --show-object-format]\n+if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Failed to determine hash algorithm:\"] \"\\n\\n$err\"]\n+\texit 1\n+}\n if {$hashalgorithm eq \"sha1\"} {\n \tset hashlength 40\n } elseif {$hashalgorithm eq \"sha256\"} {\n@@ -1160,46 +1173,52 @@ if {$_gitdir eq \".\"} {\n \tset _gitdir [pwd]\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n-\n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n+# Handle bare repository and determine working tree\n+if {[is_bare]} {\n+\t# Bare repository: only allowed for certain subcommands\n+\tif {![is_enabled bare]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" [file normalize $_gitdir]]\n \t\texit 1\n \t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n-\t\texit 1\n+\t# Allowed bare repo does not have a worktree\n+\tset _gitworktree {}\n+} else {\n+\t# Non-bare repository: we must find a worktree\n+\tif {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {\n+\t\t# The only acceptable failure is when we are inside\n+\t\t# the .git directory of a regular repository.\n+\t\tset inside_gitdir 0\n+\t\tcatch {set inside_gitdir [git rev-parse --is-inside-git-dir]}\n+\t\tif {$inside_gitdir eq {true} && [file tail $_gitdir] eq {.git}} {\n+\t\t\t# Use the parent directory as worktree (historic behavior)\n+\t\t\tset _gitworktree [file normalize [file dirname $_gitdir]]\n+\t\t} else {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Cannot determine working tree:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n \t}\n+\n \tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [mc \"Cannot determine working tree (unexpected empty result)\"]\n+\t\texit 1\n \t}\n+\n \tif {[catch {cd $_gitworktree} err]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Cannot move to working directory:\"] \"\\n\\n$err\"]\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n }\n+\n+# Derive a human-readable repository name\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1207,6 +1226,7 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n+# Export the final paths\n set env(GIT_DIR) $_gitdir\n set env(GIT_WORK_TREE) $_gitworktree\n \n-- \n2.52.0.windows.1\n\n"},{"id":"542818","messageId":"tencent_16BC933B6ECB562A74F4EE334828A2D23605@qq.com","threadId":"65531","inReplyTo":"20260506202751.3294-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v6 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-06T20:27:51Z","receivedAt":"2026-05-06T20:29:13Z","isPatch":true,"body":"Users expect these two invocations to be equivalent:\n\n    GIT_WORK_TREE=/some/path GIT_DIR=/some/path/.git git gui\n    git -C /some/path gui\n\nCurrently, the environment variable variant often brings up the\nrepository picker or ignores the requested worktree because\nGIT_WORK_TREE is processed too late.  Moreover, after determining\nthe working tree, git-gui unconditionally exports GIT_WORK_TREE.\nWhen no worktree is found (e.g., in a bare repository with a\nread-only subcommand like blame), an empty value is exported, which\nconfuses commands like `git branch --show-current`.\n\nFix both issues:\n\n- Introduce resolve_initial_environment, which is called before any\n  repository detection.  When both GIT_DIR and GIT_WORK_TREE are\n  set, it changes to the specified worktree, verifies that it is a\n  valid worktree, and adopts the given GIT_DIR.  In this case, the\n  subsequent automatic detection is skipped.  After successful\n  validation, GIT_WORK_TREE is unset so that later steps do not\n  inherit it.\n\n- At the end of startup, only export GIT_DIR.  The current directory\n  is already at the top of the worktree (when a worktree exists), so\n  Git can discover the worktree automatically.  Not exporting an\n  empty GIT_WORK_TREE fixes `git gui blame` in bare repositories.\n\nWhen only GIT_DIR is set without GIT_WORK_TREE, the existing logic\nremains unchanged for backward compatibility.  Setting only\nGIT_WORK_TREE is not a valid Git use-case and is silently ignored.\n\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 84 ++++++++++++++++++++++++++++++++++++----------\n 1 file changed, 66 insertions(+), 18 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 1191e6654c..ca651dc576 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1122,25 +1122,69 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc resolve_initial_environment {} {\n+\tglobal _gitdir env\n+\n+\t# Only act if both GIT_DIR and GIT_WORK_TREE are set\n+\tif {[info exists env(GIT_DIR)] && [info exists env(GIT_WORK_TREE)]} {\n+\t\t# Validate GIT_DIR by resolving its absolute path\n+\t\tif {[catch {set _abs [git rev-parse --absolute-git-dir]} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Invalid GIT_DIR:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\n+\t\t# Change current directory to GIT_WORK_TREE\n+\t\tif {[catch {cd $env(GIT_WORK_TREE)} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Cannot change to GIT_WORK_TREE:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\n+\t\t# Verify that GIT_WORK_TREE is a valid Git worktree\n+\t\tif {[catch {git rev-parse --show-toplevel} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"GIT_WORK_TREE is not a valid worktree:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\n+\t\t# Use repository path specified by environment variables\n+\t\tset _gitdir $_abs\n+\t\tset ::_prefix {}\n+\n+\t\t# Unset GIT_WORK_TREE to prevent it from being inherited by child processes\n+\t\tunset env(GIT_WORK_TREE)\n+\n+\t\treturn 1\n+\t}\n+\n+\t# Other cases (only GIT_DIR, only GIT_WORK_TREE, or neither):\n+\t# Do nothing here and let the existing logic handle it later\n+\treturn 0\n+}\n+set force_gitdir [resolve_initial_environment]\n+\n set picked 0\n-if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string; this is handled by\n-\t\t# the startup safety checks below\n-\t\tset _gitdir [git rev-parse --git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n-\t} err]} {\n-\tload_config 1\n-\tapply_config\n-\tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n-\t\texit 1\n+if {!$force_gitdir} {\n+\tif {[catch {\n+\t\t\tset _gitdir $env(GIT_DIR)\n+\t\t\tset _prefix {}\n+\t\t\t}]\n+\t\t&& [catch {\n+\t\t\t# beware that from the .git dir this sets _gitdir to .\n+\t\t\t# and _prefix to the empty string; this is handled by\n+\t\t\t# the startup safety checks below\n+\t\t\tset _gitdir [git rev-parse --git-dir]\n+\t\t\tset _prefix [git rev-parse --show-prefix]\n+\t\t} err]} {\n+\t\tload_config 1\n+\t\tapply_config\n+\t\tchoose_repository::pick\n+\t\tif {![file isdirectory $_gitdir]} {\n+\t\t\texit 1\n+\t\t}\n+\t\tset picked 1\n \t}\n-\tset picked 1\n }\n \n if {![file isdirectory $_gitdir]} {\n@@ -1228,7 +1272,11 @@ if {[lindex $_reponame end] eq {.git}} {\n \n # Export the final paths\n set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n+# Do not export `GIT_WORK_TREE`. The current directory is already\n+# the top-level of the working tree (if it exists), and Git will\n+# automatically identify the working tree. For bare repositories,\n+# `_gitworktree` is empty, and exporting an empty value would cause\n+# commands like `git branch --show-current` to fail\n \n ######################################################################\n ##\n-- \n2.52.0.windows.1\n\n"},{"id":"542831","messageId":"2bfff1dc-e9f1-406a-9757-25a63761e32f@gmail.com","threadId":"65531","inReplyTo":"7a963eec-80d2-4605-8cb1-52fb7bc9cf8e@gmail.com","subject":"Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-07T05:09:17Z","receivedAt":"2026-05-07T05:09:20Z","isPatch":true,"body":"\n\nOn 5/6/26 10:05 AM, Mark Levedahl wrote:\n>\n> On 5/6/26 8:57 AM, Johannes Sixt wrote:\n>> Am 06.05.26 um 13:27 schrieb Mark Levedahl:\n>>> A git repository (gitdir) can have config.bare true | false | not set\n>>> git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the\n>>> current directory has core.bare==true. This happens whether the call is from inside the\n>>> gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a\n>>> symlink or a gitfile link to the gitdir. This call never tells you what directory you are\n>>> actually in.\n>> OK. But how does \"find out which directory we are in\" come into play\n>> here? If we find a bare repository, we do not need a worktree. If we are\n>> in a non-bare repository, we can find the worktree with `rev-parse\n>> --show-toplevel`.\nThe influence of config variable core.bare in a git repository breaks the design I\ncontemplated using rev-parse --is-inside-work-tree. core.bare is a strange tri-state item,\nusually only set if the repository is explicitly bare. With core.bare undefined, rev-parse\ncan give different answers inside and outside a gitdir. For instance\n      rev-parse --is-bare-repository gives true inside a repository, and false outside,\n--is-inside-work-tree is also influenced by core.bare, so results cannot be interpreted as\nI initially thought.\n\nAnyway, after some experiments, the minimal sufficient tests I find for blame|browser and\ngui|citool are different:\n\n- blame|browser need a readable gitdir, and do not need a worktree. The current directory\ncan be inside the gitdir, core.bare is irrelevant.\n\n    a sufficient startup test:\n\n        git rev-parse --git-dir returns the name of a gitdir, rather than throwing an error.\n               core.bare and being inside/outside of the gitdir do affect error / no error.\n                (the return value may be a relative path, so can depend upon current\ndirectory)\n\n    only GIT_DIR should be exported. GIT_WORK_TREE should not be in env.\n\n- gui|citool, require a gitdir, a worktree outside the gitdir, and core.bare is not true\n\n    a sufficient and minimal startup test:\n\n        git rev-parse --show-toplevel returns the worktree root, rather than throwing an\nerror.\n                core.bare = true in the gitdir will cause error in the above.\n                being inside the gitdir causes error in the above.\n\n        But, starting in a .git directory supporting a worktree is allowed by the fallback\nlogic\n\n        git rev-parse --git-dir returns '.', (current dir is the root of the gitdir),\nrather than throwing an error.\n        the current directory is named \".git\", meaning the parent dir is this gitdir's\nworktree if core.bare is not true\n        git -C .. rev-parse --show-toplevel  returns the worktree root: (will throw an\nerror if core.bare is true)\n\n    the current directory should be changed to the worktree root.\n    GIT_DIR and GIT_WORK_TREE should be exported to env.\n\nMark\n"},{"id":"542854","messageId":"54d3c28a-040e-470c-b061-8d3a1cfe4257@gmail.com","threadId":"65531","inReplyTo":"tencent_16BC933B6ECB562A74F4EE334828A2D23605@qq.com","subject":"Re: [PATCH v6 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-07T15:50:32Z","receivedAt":"2026-05-07T15:50:34Z","isPatch":true,"body":"\n\nOn 5/6/26 4:27 PM, Shroom Moo wrote:\n> +proc resolve_initial_environment {} {\n> +\tglobal _gitdir env\n> +\n> +\t# Only act if both GIT_DIR and GIT_WORK_TREE are set\n> +\tif {[info exists env(GIT_DIR)] && [info exists env(GIT_WORK_TREE)]} {\n> +\t\t# Validate GIT_DIR by resolving its absolute path\n> +\t\tif {[catch {set _abs [git rev-parse --absolute-git-dir]} err]} {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [strcat [mc \"Invalid GIT_DIR:\"] \"\\n\\n$err\"]\n> +\t\t\texit 1\n> +\t\t}\n> +\n> +\t\t# Change current directory to GIT_WORK_TREE\n> +\t\tif {[catch {cd $env(GIT_WORK_TREE)} err]} {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [strcat [mc \"Cannot change to GIT_WORK_TREE:\"] \"\\n\\n$err\"]\n> +\t\t\texit 1\n> +\t\t}\nGIT_DIR could be relative to the startup directory. The absolute variant should be\nexported before the next step.\n\n\n\n> +\n> +\t\t# Verify that GIT_WORK_TREE is a valid Git worktree\n> +\t\tif {[catch {git rev-parse --show-toplevel} err]} {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [strcat [mc \"GIT_WORK_TREE is not a valid worktree:\"] \"\\n\\n$err\"]\n> +\t\t\texit 1\n> +\t\t}\n> +\n> +\t\t# Use repository path specified by environment variables\n> +\t\tset _gitdir $_abs\n> +\t\tset ::_prefix {}\n> +\n> +\t\t# Unset GIT_WORK_TREE to prevent it from being inherited by child processes\n> +\t\tunset env(GIT_WORK_TREE)\n> +\n\nThere are many places in the code that have pairs of set/unset env(GIT_DIR) &\nenv(GIT_WORK_TREE).\nNow that we have cases where $_gitworktree == {} and we don't want that exported, and\nthese are all unguarded.\n\nPerhaps we need to create set/unset procs that do the right thing (setx assures\nGIT_WORK_TREE is not in the env when $_gitworktree == {}), unsetx does not thow and error\nwhen unset env(GIT_WORK_TREE) fails as the value doesn't exist, and invoke those common\nprocs to instead.\n\nMark\n"},{"id":"542937","messageId":"20260509084846.1694-1-bootaina702@gmail.com","threadId":"65531","inReplyTo":"54d3c28a-040e-470c-b061-8d3a1cfe4257@gmail.com","subject":"Re: [PATCH v6 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Aina Boot","fromEmail":"bootaina702@gmail.com","sentAt":"2026-05-09T08:46:56Z","receivedAt":"2026-05-09T08:49:08Z","isPatch":true,"body":"After observing, I’d like to propose a clearer way to manage\nGIT_WORK_TREE and GIT_DIR environment variables.\n\nFive procedures in total:\n1. set_worktree_env – sets GIT_WORK_TREE in the environment only if\n$_gitworktree is non-empty.\n2. unset_worktree_env – safely unsets GIT_WORK_TREE (ignores if it’s\nnot set).\n3. clear_git_env – unsets both GIT_DIR and GIT_WORK_TREE before\nentering a submodule context.\n4. capture_git_env – captures the current GIT_DIR and GIT_WORK_TREE\nvalues for later restoration.\n5. restore_git_env – restores the saved values, setting GIT_WORK_TREE\nonly if it was previously non-empty.\n\nThese would replace scattered, unguarded set/unset env(GIT_WORK_TREE)\ncalls and ensure we don’t leave GIT_WORK_TREE defined when there is no\nactual worktree. It also makes the intention more explicit and reduces\nrepetitive error-catching logic.\n\nAina\n"},{"id":"542938","messageId":"tencent_CAC52D0829E651C1CF21A1B94AEDF00D6E07@qq.com","threadId":"65531","inReplyTo":"20260509084846.1694-1-bootaina702@gmail.com","subject":"Re: [PATCH v6 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-09T09:55:37Z","receivedAt":"2026-05-09T09:55:56Z","isPatch":true,"body":"These are functional, yet somewhat complex. Only 1 and 2 are necessary \nin this adjustment, I suppose. \n\nSaving and restoring old values in do_gitk and do_git_gui actually \ndoes not require additional encapsulation because: \n- First, GIT_DIR always exists, and directly using set and catch \n{unset} is inherently safe. \n- Second, the set/unset operations for GIT_WORK_TREE are already \nproperly encapsulated. \n\nSince the save-restore pattern only appears in these two places, \nintroducing capture_git_env / restore_git_env adds more conceptual \noverhead than what it saves. \n\nShroom\n\n"},{"id":"542941","messageId":"tencent_66A1C2CDB9D5B764A5B4468D3F11845A2A09@qq.com","threadId":"65531","inReplyTo":"tencent_DDD6467B3F6184562B51C23BB9DBB79EA409@qq.com","subject":"[PATCH v7 0/3] git-gui: robustify startup and fix environment handling","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-09T13:37:52Z","receivedAt":"2026-05-09T13:44:17Z","isPatch":true,"body":"This series addresses the startup crash introduced by Git commit\n\"2d92ab32fd (\"rev-parse: make --show-toplevel without a worktree an\nerror\", 2019-11-19)\", which causes `git gui` to die with a Tcl error\nwhen a worktree is missing (e.g. inside a .git directory without a\nworking tree, or in a bare repository).\n\nAdditionally, it resolves two historically inconsistent behaviours:\n- The \"Visualize ... History\" menu items were enabled in bare\n  repositories but triggered Tcl errors due to the missing worktree.\n- `GIT_WORK_TREE` and `GIT_DIR` environment variables were not\n  respected early enough, so `GIT_WORK_TREE=/some/path git gui`\n  often ignored the explicit worktree and brought up the repository\n  picker, and an exported empty `GIT_WORK_TREE` confused commands\n  like `git branch --show-current` in bare repositories.\n\nShroom Moo (3):\n  git-gui: restructure repository startup\n  git-gui: disable gitk visualization when no worktree available\n  git-gui: handle GIT_DIR and GIT_WORK_TREE early\n\n git-gui/git-gui.sh | 187 ++++++++++++++++++++++++++++++++++-----------\n 1 file changed, 141 insertions(+), 46 deletions(-)\n\n-- \n2.52.0.windows.1\n\n"},{"id":"542942","messageId":"tencent_2D0A6C14E8B34348B6F236BC8E7B66AB5105@qq.com","threadId":"65531","inReplyTo":"20260509133756.1367-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v7 1/3] git-gui: restructure repository startup","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-09T13:37:53Z","receivedAt":"2026-05-09T13:44:21Z","isPatch":true,"body":"When git-gui is started inside a .git directory of a non-bare\nrepository, it should treat the parent directory as the worktree,\nas it did before commit 2d92ab32fd (rev-parse: make --show-toplevel\nwithout a worktree an error, 2019-11-19).  However, a bare repository\nor a separated gitdir without a worktree must be rejected early.\n\nProtect the previously unguarded calls to `git rev-parse\n--show-object-format` and `--show-toplevel`.  Restructure the startup\nsequence to:\n\n- Check for a bare repository right after loading the config.  If the\n  repository is bare and the current subcommand does not allow bare\n  repos (e.g. normal commit mode), show \"Cannot use bare repository\"\n  and exit.\n\n- When `rev-parse --show-toplevel` fails and the repository is\n  non-bare, the gitdir path ends with \".git\", and we are inside that\n  gitdir, use the parent directory as the worktree.  This preserves\n  the ability to start git-gui from within a regular repository’s\n  .git directory, which was intentionally supported since 87cd09f43e56\n  (git-gui: work from the .git dir, 2010-01-23).\n\n- Otherwise, show a descriptive error and exit.\n\n- Wrap `rev-parse --show-object-format` in a catch to avoid a crash\n  when the repository configuration is broken (e.g. core.worktree\n  pointing to an invalid path).\n\nAlso removes the old `_prefix`‑based fallback that computed a relative\npath to the worktree top from a subdirectory, and the unconditional\n`[file dirname $_gitdir]` guess.  Both are unnecessary now that\n`rev‑parse --show‑toplevel` directly provides the absolute top‑level\npath and we can `cd` to it.  The guess is further unsafe in\nmulti‑worktree setups, where a gitdir may have more than one worktree.\nThe only remaining fallback is the explicit “.git directory” rule for\nnon‑bare repositories, which mirrors the historical behaviour.\nAdditionally, only export GIT_WORK_TREE when it is not empty, to avoid\nconfusing commands in bare-repository subcommands.\n\nThis fixes the fatal Tcl error when the working tree is missing, while\nkeeping the .git startup feature and avoiding any automatic directory\nswitching that could be dangerous in multi‑worktree setups.\n\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 76 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 49 insertions(+), 27 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 23fe76e498..9eb93a76b5 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1129,7 +1129,8 @@ if {[catch {\n \t\t}]\n \t&& [catch {\n \t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n+\t\t# and _prefix to the empty string; this is handled by\n+\t\t# the startup safety checks below\n \t\tset _gitdir [git rev-parse --git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n@@ -1142,8 +1143,20 @@ if {[catch {\n \tset picked 1\n }\n \n+if {![file isdirectory $_gitdir]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat \n+\t\t[mc \"Git directory not found:\"] \"\\n\\n$_gitdir\\n\\n\" \\\n+\t\t[mc \"Please ensure GIT_DIR points to a valid Git repository\"]]\n+\texit 1\n+}\n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n-set hashalgorithm [git rev-parse --show-object-format]\n+if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Failed to determine hash algorithm:\"] \"\\n\\n$err\"]\n+\texit 1\n+}\n if {$hashalgorithm eq \"sha1\"} {\n \tset hashlength 40\n } elseif {$hashalgorithm eq \"sha256\"} {\n@@ -1160,46 +1173,52 @@ if {$_gitdir eq \".\"} {\n \tset _gitdir [pwd]\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n-\n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n+# Handle bare repository and determine working tree\n+if {[is_bare]} {\n+\t# Bare repository: only allowed for certain subcommands\n+\tif {![is_enabled bare]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" [file normalize $_gitdir]]\n \t\texit 1\n \t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n-\t\texit 1\n+\t# Allowed bare repo does not have a worktree\n+\tset _gitworktree {}\n+} else {\n+\t# Non-bare repository: we must find a worktree\n+\tif {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {\n+\t\t# The only acceptable failure is when we are inside\n+\t\t# the .git directory of a regular repository.\n+\t\tset inside_gitdir 0\n+\t\tcatch {set inside_gitdir [git rev-parse --is-inside-git-dir]}\n+\t\tif {$inside_gitdir eq {true} && [file tail $_gitdir] eq {.git}} {\n+\t\t\t# Use the parent directory as worktree (historic behavior)\n+\t\t\tset _gitworktree [file normalize [file dirname $_gitdir]]\n+\t\t} else {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Cannot determine working tree:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n \t}\n+\n \tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [mc \"Cannot determine working tree (unexpected empty result)\"]\n+\t\texit 1\n \t}\n+\n \tif {[catch {cd $_gitworktree} err]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Cannot move to working directory:\"] \"\\n\\n$err\"]\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n }\n+\n+# Derive a human-readable repository name\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1207,8 +1226,11 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n+# Export the final paths\n set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n+if {$_gitworktree ne {}} {\n+\tset env(GIT_WORK_TREE) $_gitworktree\n+}\n \n ######################################################################\n ##\n-- \n2.52.0.windows.1\n\n"},{"id":"542943","messageId":"tencent_C4AD92361C8D7B76EB4C8A6F14EA33496805@qq.com","threadId":"65531","inReplyTo":"20260509133756.1367-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v7 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-09T13:37:55Z","receivedAt":"2026-05-09T13:44:29Z","isPatch":true,"body":"Users expect these two invocations to be equivalent:\n\n    GIT_WORK_TREE=/some/path GIT_DIR=/some/path/.git git gui\n    git -C /some/path gui\n\nCurrently, the environment variable variant often brings up the\nrepository picker or ignores the requested worktree because\nGIT_WORK_TREE is processed too late.  Moreover, after determining\nthe working tree, git-gui unconditionally exports GIT_WORK_TREE.\nWhen no worktree is found (e.g., in a bare repository with a\nread-only subcommand like blame), an empty value is exported, which\nconfuses commands like `git branch --show-current`.\n\nFix both issues:\n\n- Introduce resolve_initial_environment, which is called before any\n  repository detection.  When both GIT_DIR and GIT_WORK_TREE are\n  set, it changes to the specified worktree, verifies that it is a\n  valid worktree, and adopts the given GIT_DIR.  In this case, the\n  subsequent automatic detection is skipped.  After successful\n  validation, GIT_WORK_TREE is unset so that later steps do not\n  inherit it.  Export the absolute GIT_DIR before changing directory\n  to ensure Git commands see the intended repository.\n\n- Introduce set_worktree_env and unset_worktree_env helper procedures\n  to safely manage the GIT_WORK_TREE environment variable:\n  set_worktree_env sets it only when $_gitworktree is non-empty,\n  and unset_worktree_env removes the variable without error.\n  Replace all direct manipulations of GIT_WORK_TREE with these\n  helpers to avoid accidentally exporting an empty value or\n  causing 'unset' errors.\n\n- At the end of startup, use set_worktree_env instead of exporting\n  GIT_WORK_TREE unconditionally.  This leaves the variable unset\n  when no worktree exists (bare repository), fixing commands that\n  would fail with an empty GIT_WORK_TREE.\n\nWhen only GIT_DIR is set without GIT_WORK_TREE, the existing logic\nremains unchanged for backward compatibility.  Setting only\nGIT_WORK_TREE is not a valid Git use-case and is silently ignored.\n\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 103 ++++++++++++++++++++++++++++++++++-----------\n 1 file changed, 79 insertions(+), 24 deletions(-)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex ff022c5bbb..1123f9855b 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -1122,25 +1122,82 @@ unset argv0dir\n ##\n ## repository setup\n \n+# Safely set/unset GIT_WORK_TREE\n+proc set_worktree_env {} {\n+    global _gitworktree\n+    if {$_gitworktree ne {}} {\n+        set ::env(GIT_WORK_TREE) $_gitworktree\n+    }\n+}\n+\n+proc unset_worktree_env {} {\n+    catch {unset ::env(GIT_WORK_TREE)}\n+}\n+\n+proc resolve_initial_environment {} {\n+\tglobal _gitdir env\n+\n+\t# Only act if both GIT_DIR and GIT_WORK_TREE are set\n+\tif {[info exists env(GIT_DIR)] && [info exists env(GIT_WORK_TREE)]} {\n+\t\t# Validate GIT_DIR by resolving its absolute path\n+\t\tif {[catch {set _abs [git rev-parse --absolute-git-dir]} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Invalid GIT_DIR:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\t\tset env(GIT_DIR) $_abs\n+\n+\t\t# Change current directory to GIT_WORK_TREE\n+\t\tif {[catch {cd $env(GIT_WORK_TREE)} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"Cannot change to GIT_WORK_TREE:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\n+\t\t# Verify that GIT_WORK_TREE is a valid Git worktree\n+\t\tif {[catch {git rev-parse --show-toplevel} err]} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup [strcat [mc \"GIT_WORK_TREE is not a valid worktree:\"] \"\\n\\n$err\"]\n+\t\t\texit 1\n+\t\t}\n+\n+\t\t# Use repository path specified by environment variables\n+\t\tset _gitdir $_abs\n+\t\tset ::_prefix {}\n+\n+\t\t# Unset GIT_WORK_TREE to prevent it from being inherited by child processes\n+\t\tunset_worktree_env\n+\n+\t\treturn 1\n+\t}\n+\n+\t# Other cases (only GIT_DIR, only GIT_WORK_TREE, or neither):\n+\t# Do nothing here and let the existing logic handle it later\n+\treturn 0\n+}\n+set force_gitdir [resolve_initial_environment]\n+\n set picked 0\n-if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string; this is handled by\n-\t\t# the startup safety checks below\n-\t\tset _gitdir [git rev-parse --git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n-\t} err]} {\n-\tload_config 1\n-\tapply_config\n-\tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n-\t\texit 1\n+if {!$force_gitdir} {\n+\tif {[catch {\n+\t\t\tset _gitdir $env(GIT_DIR)\n+\t\t\tset _prefix {}\n+\t\t\t}]\n+\t\t&& [catch {\n+\t\t\t# beware that from the .git dir this sets _gitdir to .\n+\t\t\t# and _prefix to the empty string; this is handled by\n+\t\t\t# the startup safety checks below\n+\t\t\tset _gitdir [git rev-parse --git-dir]\n+\t\t\tset _prefix [git rev-parse --show-prefix]\n+\t\t} err]} {\n+\t\tload_config 1\n+\t\tapply_config\n+\t\tchoose_repository::pick\n+\t\tif {![file isdirectory $_gitdir]} {\n+\t\t\texit 1\n+\t\t}\n+\t\tset picked 1\n \t}\n-\tset picked 1\n }\n \n if {![file isdirectory $_gitdir]} {\n@@ -1228,9 +1285,7 @@ if {[lindex $_reponame end] eq {.git}} {\n \n # Export the final paths\n set env(GIT_DIR) $_gitdir\n-if {$_gitworktree ne {}} {\n-\tset env(GIT_WORK_TREE) $_gitworktree\n-}\n+set_worktree_env\n \n ######################################################################\n ##\n@@ -2077,12 +2132,12 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n \t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n+\t\t\tunset_worktree_env\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_worktree_env\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2111,7 +2166,7 @@ proc do_git_gui {} {\n \t\t# see note in do_gitk about unsetting these vars when\n \t\t# running tools in a submodule\n \t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n+\t\tunset_worktree_env\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n@@ -2119,7 +2174,7 @@ proc do_git_gui {} {\n \t\tsafe_exec_bg [concat $exe gui]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_worktree_env\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.52.0.windows.1\n\n"},{"id":"542944","messageId":"tencent_A6BA86DF71476C6948398C167C0E0919550A@qq.com","threadId":"65531","inReplyTo":"20260509133756.1367-1-egg_mushroomcow@foxmail.com","subject":"[PATCH v7 2/3] git-gui: disable gitk visualization when no worktree available","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-09T13:37:54Z","receivedAt":"2026-05-09T13:51:51Z","isPatch":true,"body":"When git-gui is started in a bare repository with the 'bare' option\nenabled (e.g., for blame/browser), there is no working tree.  The\n\"Visualize Current Branch's History\" and \"Visualize All Branch\nHistory\" menu items remain enabled, but clicking them triggers a Tcl\nerror because do_gitk tries to change directory to an empty\n_gitworktree.\n\nFix this by disabling the two visualization menu items when the\nrepository is bare and the 'bare' option is active.  Also update\ncurrent_branch_write to keep the state consistent when the branch\nchanges, and add a defensive check in do_gitk to avoid the error\nshould the menu state somehow become out of sync.\n\nThis complements the startup sequence improvements in the previous\ncommit, which already correctly identifies bare repositories and\nleaves _gitworktree empty in such cases.\n\nHelped-by: Mark Levedahl <mlevedahl@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n---\n git-gui/git-gui.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\nindex 9eb93a76b5..ff022c5bbb 100755\n--- a/git-gui/git-gui.sh\n+++ b/git-gui/git-gui.sh\n@@ -2036,6 +2036,10 @@ proc do_gitk {revs {is_submodule false}} {\n \t#\n \tset exe [_which gitk -script]\n \tset cmd [list [info nameofexecutable] $exe]\n+\tif {$_gitworktree eq {}} {\n+\t\terror_popup [mc \"Cannot visualize history: no working tree\"]\n+\t\treturn\n+\t}\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find gitk in PATH\"]\n \t} else {\n@@ -2659,6 +2663,13 @@ set ui_visualize_current [.mbar.repository index last]\n .mbar.repository add command \\\n \t-label [mc \"Visualize All Branch History\"] \\\n \t-command {do_gitk --all}\n+set ui_visualize_all [.mbar.repository index last]\n+\n+# Cannot work without a working tree\n+if {[is_bare] && [is_enabled bare]} {\n+\t.mbar.repository entryconf $ui_visualize_current -state disabled\n+\t.mbar.repository entryconf $ui_visualize_all   -state disabled\n+}\n .mbar.repository add separator\n \n proc current_branch_write {args} {\n@@ -2667,6 +2678,13 @@ proc current_branch_write {args} {\n \t\t-label [mc \"Browse %s's Files\" $current_branch]\n \t.mbar.repository entryconf $::ui_visualize_current \\\n \t\t-label [mc \"Visualize %s's History\" $current_branch]\n+\tif {[is_bare] && [is_enabled bare]} {\n+\t\t.mbar.repository entryconf $::ui_visualize_current -state disabled\n+\t\t.mbar.repository entryconf $::ui_visualize_all   -state disabled\n+\t} else {\n+\t\t.mbar.repository entryconf $::ui_visualize_current -state normal\n+\t\t.mbar.repository entryconf $::ui_visualize_all   -state normal\n+\t}\n }\n trace add variable current_branch write current_branch_write\n \n-- \n2.52.0.windows.1\n\n\n"},{"id":"543313","messageId":"50df7f28-c63c-4762-b542-b888ea3604c0@gmail.com","threadId":"65531","inReplyTo":"tencent_66A1C2CDB9D5B764A5B4468D3F11845A2A09@qq.com","subject":"Re: [PATCH v7 0/3] git-gui: robustify startup and fix environment handling","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:28:51Z","receivedAt":"2026-05-14T14:28:53Z","isPatch":true,"body":"On 5/9/26 9:37 AM, Shroom Moo wrote:\n> Shroom Moo (3):\n>   git-gui: restructure repository startup\n>   git-gui: disable gitk visualization when no worktree available\n>   git-gui: handle GIT_DIR and GIT_WORK_TREE early\n>\n>\nAfter careful consideration, I find starting off by fixing what is broken in git-gui about\nusing a bare-repository, and letting git core handle GIT_DIR and GIT_WORK_TREE, leads to a\nmuch more complete and different solution. A patch series (attempting to) do so will follow.\n\nMark\n"},{"id":"543314","messageId":"20260514143322.865587-1-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"50df7f28-c63c-4762-b542-b888ea3604c0@gmail.com","subject":"[PATCH v1 00/11] Improve git gui operation without a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:11Z","receivedAt":"2026-05-14T14:33:43Z","isPatch":true,"body":"git gui has a number of inter-related problems that result in problems\nduring startup from anything but a checked out worktree pointing at a\nvalid git repository. Some of the symptoms are:\n- blame / browser subcommands, and launching gitk, are intended to be\n  useful without a worktree, but fail to work.\n- unlike git, git-gui is supposed to use the parent directory as a\n  worktree if started from the .git subdirectory in the very common\n  single worktree + embedded git repository format. This does not\n  work.\n- git-gui includes a repository picker allowing a user select a worktree\n  from a list and/or start a new repo+worktree: this dialog appears at\n  unexpected times, masking useful error feedback on configuration\n  problems.\n\nThis patch series addresses the above issues, substantially rewriting\nthe blame / browser command line process, the initial repository and\nworktree discovery processes, and using git rev-parse when possible to\nhandle repository / worktree discovery including any specification of\nGIT_DIR or GIT_WORK_TREE to reduce the future likelihood of conflict\nwith command line git. This also allows explicit user control to avoid\nthe repository picker masking a configuration error.\n\nNote: I question why git-gui ever exports GIT_WORK_TREE. If it is not\nempty, that is the current directory when startup is complete and any\ngit command will use the current directory as the worktree. If empty,\nthere is no worktree and the current directory should be (and after this\nseries, is) at the toplevel of the gitdir: again, there is nothing to\ncommunicate to another process. If a process being launched needs a\ndifferent worktree, that should be the startup directory given to the\nprocess without changing git-gui's current directory.\n\nMark Levedahl (11):\n  git-gui: allow specifying path '.' to the browser\n  git-gui: refactor browser / blame argument parsing\n  git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE\n  git-gui: put choose_repository::pick in a proc\n  git-gui: use --absolute-git-dir\n  git gui: GIT_DIR / GIT_WORK_TREE make any discovery error fatal\n  git-gui: use rev-parse exclusively to find a repository\n  git-gui: simplify [is_bare] to report if a worktree is known\n  git-gui: support using repository parent dir as a worktree\n  git-gui: improve worktree discovery\n  git-gui: add gui and pick as explicit subcommands\n\n git-gui.sh | 276 ++++++++++++++++++++++++++---------------------------\n 1 file changed, 135 insertions(+), 141 deletions(-)\n\n-- \n2.54.0.99.14\n\n"},{"id":"543315","messageId":"20260514143322.865587-2-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 01/11] git-gui: allow specifying path '.' to the browser","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:12Z","receivedAt":"2026-05-14T14:34:05Z","isPatch":true,"body":"Invoking \"git-gui browser rev .\" should show the file browser for the\ncommitish rev, starting at the root directory. This errors out in\nnormalize_relpath because the '.' is removed, yielding an empty list as\nargument to [file join ...]. Fix this.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 23fe76e..6048f92 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2965,7 +2965,11 @@ proc normalize_relpath {path} {\n \t\t}\n \t\tlappend elements $item\n \t}\n-\treturn [eval file join $elements]\n+\tif {$elements ne {}} {\n+\t\treturn [eval file join $elements]\n+\t} else {\n+\t\treturn {}\n+\t}\n }\n \n # -- Not a normal commit type invocation?  Do that instead!\n-- \n2.54.0.99.14\n\n"},{"id":"543316","messageId":"20260514143322.865587-3-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 02/11] git-gui: refactor browser / blame argument parsing","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:13Z","receivedAt":"2026-05-14T14:34:06Z","isPatch":true,"body":"git-gui has subcommands blame and browser, both of which accept a\npathname, possibly preceded by a commit-ish item to specify a revision.\nAlso, blame can take a first argument that gives a line number to focus.\n\nThe command line parser for the above is more complex than needed, and\ncannot work without a worktree as the pathname objects are checked\nagainst the current worktree for existence. This also precludes naming a\ndirectory or file that does not exist on the currently checked out\nbranch.\n\nSo, replace this with a simpler parser that looks at argument number and\nnumber of arguments to know what value to expect. The blame and browser\nbackends already have error checking with diagnostic information, so\ndefer most error checking to those. Also, allow a line-number selection\nto be given and silently ignored for the browser, further simplifying\nthis code.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 66 +++++++++++++-----------------------------------------\n 1 file changed, 16 insertions(+), 50 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 6048f92..a951fcd 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2986,51 +2986,34 @@ blame {\n \tset head {}\n \tset path {}\n \tset jump_spec {}\n-\tset is_path 0\n+\tset nargs [llength $argv]\n+\tif {$nargs < 1} {\n+\t\tusage\n+\t}\n+\tset argn 0\n \tforeach a $argv {\n-\t\tset p [file join $_prefix $a]\n+\t\tset argn [expr {$argn + 1}]\n \n-\t\tif {$is_path || [file exists $p]} {\n-\t\t\tif {$path ne {}} usage\n-\t\t\tset path [normalize_relpath $p]\n-\t\t\tbreak\n-\t\t} elseif {$a eq {--}} {\n-\t\t\tif {$path ne {}} {\n-\t\t\t\tif {$head ne {}} usage\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n+\t\tif {$argn < $nargs} {\n+\t\t\t# revision or line number\n+\t\t\tif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n+\t\t\t\tset jump_spec [list $lnum]\n+\t\t\t} else {\n+\t\t\t\tset head $a\n \t\t\t}\n-\t\t\tset is_path 1\n-\t\t} elseif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n-\t\t\tif {$jump_spec ne {} || $head ne {}} usage\n-\t\t\tset jump_spec [list $lnum]\n-\t\t} elseif {$head eq {}} {\n-\t\t\tif {$head ne {}} usage\n-\t\t\tset head $a\n-\t\t\tset is_path 1\n-\t\t} else {\n-\t\t\tusage\n-\t\t}\n-\t}\n-\tunset is_path\n-\n-\tif {$head ne {} && $path eq {}} {\n-\t\tif {[string index $head 0] eq {/}} {\n-\t\t\tset path [normalize_relpath $head]\n-\t\t\tset head {}\n \t\t} else {\n-\t\t\tset path [normalize_relpath $_prefix$head]\n-\t\t\tset head {}\n+\t\t\tset path [normalize_relpath $a]\n \t\t}\n \t}\n \n \tif {$head eq {}} {\n \t\tload_current_branch\n+\t\tset head $current_branch\n \t} else {\n \t\tif {[regexp [string map \"@@ [expr $hashlength - 1]\" {^[0-9a-f]{1,@@}$}] $head]} {\n \t\t\tif {[catch {\n-\t\t\t\t\tset head [git rev-parse --verify $head]\n-\t\t\t\t} err]} {\n+\t\t\t\tset head [git rev-parse --verify $head]\n+\t\t\t} err]} {\n \t\t\t\tif {[tk windowingsystem] eq \"win32\"} {\n \t\t\t\t\ttk_messageBox -icon error -title [mc Error] -message $err\n \t\t\t\t} else {\n@@ -3046,26 +3029,9 @@ blame {\n \tswitch -- $subcommand {\n \tbrowser {\n \t\tif {$jump_spec ne {}} usage\n-\t\tif {$head eq {}} {\n-\t\t\tif {$path ne {} && [file isdirectory $path]} {\n-\t\t\t\tset head $current_branch\n-\t\t\t} else {\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n-\t\t\t}\n-\t\t}\n \t\tbrowser::new $head $path\n \t}\n \tblame   {\n-\t\tif {$head eq {} && ![file exists $path]} {\n-\t\t\tcatch {wm withdraw .}\n-\t\t\ttk_messageBox \\\n-\t\t\t\t-icon error \\\n-\t\t\t\t-type ok \\\n-\t\t\t\t-title [mc \"git-gui: fatal error\"] \\\n-\t\t\t\t-message [mc \"fatal: cannot stat path %s: No such file or directory\" $path]\n-\t\t\texit 1\n-\t\t}\n \t\tblame::new $head $path $jump_spec\n \t}\n \t}\n-- \n2.54.0.99.14\n\n"},{"id":"543317","messageId":"20260514143322.865587-5-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 04/11] git-gui: put choose_repository::pick in a proc","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:15Z","receivedAt":"2026-05-14T14:34:08Z","isPatch":true,"body":"git-gui includes a 'repository picker', which allows creating a new\nrepository + worktree, or selecting a worktree from a recent list.\ngit-gui runs the picker when a valid git repository is not found. All of\nthe code for this is embedded in the discovery process block, making the\nlatter more difficult to read, and also making things more difficult if\nwe want to have an explicit 'pick' subcommand to force this to run.\n\nLet's move this invocation and supporting code to a separate proc,\naiding in subsequent refactoring. Assure GIT_DIR and GIT_WORK_TREE are\nunset, configuration is loaded, ant that _gitdir is correctly set\nafterwards. As this is invoked before worktree discovery, later code\nwill set that anyway so need not be included here.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 18 +++++++++++-------\n 1 file changed, 11 insertions(+), 7 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 387cad6..0b73c35 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1139,6 +1139,16 @@ proc unset_gitdir_vars {} {\n }\n \n set picked 0\n+proc pick_repo {} {\n+\tunset_gitdir_vars\n+\tload_config 1\n+\tapply_config\n+\tchoose_repository::pick\n+\tset _gitdir [git rev-parse --absolute-git-dir]\n+\tset _prefix {}\n+\tset picked 1\n+}\n+\n if {[catch {\n \t\tset _gitdir $env(GIT_DIR)\n \t\tset _prefix {}\n@@ -1149,13 +1159,7 @@ if {[catch {\n \t\tset _gitdir [git rev-parse --git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n-\tload_config 1\n-\tapply_config\n-\tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n-\t\texit 1\n-\t}\n-\tset picked 1\n+\t\tpick_repo\n }\n \n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n-- \n2.54.0.99.14\n\n"},{"id":"543318","messageId":"20260514143322.865587-6-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 05/11] git-gui: use --absolute-git-dir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:16Z","receivedAt":"2026-05-14T14:34:08Z","isPatch":true,"body":"git-gui uses git rev-parse --git-dir to get the pathname of the\ndiscovered git repository. The returned value can be relative, and is\n'.' if the current directory is the top of the repository directory\nitself.  git-gui has code to change '.' to [pwd] in this case so that\nsubsequent logic runs.\n\nBut, git rev-parse supports --absolute-git-dir from fac60b8925\n(\"rev-parse: add option for absolute or relative path formatting\",\n2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\nthis more useful form is always available. Use --absolute-git-dir to\nalways get an absolute path, avoiding the need for other checks.\n---\n git-gui.sh | 10 ++--------\n 1 file changed, 2 insertions(+), 8 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 0b73c35..c2cf5f1 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1156,7 +1156,7 @@ if {[catch {\n \t&& [catch {\n \t\t# beware that from the .git dir this sets _gitdir to .\n \t\t# and _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --git-dir]\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n \t\tpick_repo\n@@ -1173,18 +1173,12 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-# we expand the _gitdir when it's just a single dot (i.e. when we're being\n-# run from the .git dir itself) lest the routines to find the worktree\n-# get confused\n-if {$_gitdir eq \".\"} {\n-\tset _gitdir [pwd]\n-}\n-\n if {![file isdirectory $_gitdir]} {\n \tcatch {wm withdraw .}\n \terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n \texit 1\n }\n+\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n-- \n2.54.0.99.14\n\n"},{"id":"543319","messageId":"20260514143322.865587-4-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 03/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:14Z","receivedAt":"2026-05-14T14:34:08Z","isPatch":true,"body":"git-gui unconditionally exports GIT_DIR and GIT_WORK_TREE to the\nenvironment, and furthmore unconditionally unsets these in many places.\nBut, GIT_WORK_TREE should be set only if it is not {} as the empty\nvalue, really meaning no work-tree is found, causes git to throw fatal\nerrors (git-gui gets the error from branch --show-current).  Fixing this\nis required to allow blame and browser to operate from a repository\nwithout a worktree.\n\nEstablish a pair of functions to remove GIT_DIR and GIT_WORK_TREE from\nthe environment, avoiding any error if they do not exist. Also, add a\nfunction to export these, but export GIT_WORK_TREE only if not empty.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 32 ++++++++++++++++++++++----------\n 1 file changed, 22 insertions(+), 10 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex a951fcd..387cad6 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1122,6 +1122,22 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc set_gitdir_vars {} {\n+\tglobal _gitdir _gitworktree env\n+\tif {$_gitdir ne {}} {\n+\t\tset env(GIT_DIR) $_gitdir\n+\t}\n+\tif {$_gitworktree ne {}} {\n+\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t}\n+}\n+\n+proc unset_gitdir_vars {} {\n+\tglobal env\n+\tcatch {unset env(GIT_DIR)}\n+\tcatch {unset env(GIT_WORK_TREE)}\n+}\n+\n set picked 0\n if {[catch {\n \t\tset _gitdir $env(GIT_DIR)\n@@ -1207,8 +1223,8 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n+# Export the final paths\n+set_gitdir_vars\n \n ######################################################################\n ##\n@@ -2050,13 +2066,11 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t# TODO we could make life easier (start up faster?) for gitk\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n-\t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n+\t\t\tunset_gitdir_vars\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2084,16 +2098,14 @@ proc do_git_gui {} {\n \n \t\t# see note in do_gitk about unsetting these vars when\n \t\t# running tools in a submodule\n-\t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n+\t\tunset_gitdir_vars\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n \n \t\tsafe_exec_bg [concat $exe gui]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.54.0.99.14\n\n"},{"id":"543320","messageId":"20260514143322.865587-7-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 06/11] git gui: GIT_DIR / GIT_WORK_TREE make any discovery error fatal","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:17Z","receivedAt":"2026-05-14T14:34:09Z","isPatch":true,"body":"git accepts any combination of GIT_DIR and GIT_WORK_TREE to override the\nnormal repository and worktree discovery process.  git-gui should accept\nany such valid configuration, but overriding the discovery process means\nthe user has assured that the combination of current directory, GIT_DIR,\nand GIT_WORK_TREE will lead to the correct repository and worktree. As\nsuch, an error found during discovery where either or both of GIT_DIR\nand GIT_WORK_TREE are set is a fatal error, no further exploration\nshould be tried.\n\nProvide a common proc to support displaying an error message and exiting\nif GIT_DIR or GIT_WORK_TREE are in the environment.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 18 ++++++++++++++++++\n 1 file changed, 18 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex c2cf5f1..2e2ddc0 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1122,6 +1122,24 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc is_gitvars_error {err} {\n+\tset havevars 0\n+\tset GIT_DIR {}\n+\tset GIT_WORK_TREE {}\n+\tcatch {set GIT_DIR $::env(GIT_DIR); set havevars 1}\n+\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE) ; set havevars 1}\n+\n+\tif {$havevars} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Invalid configuration:\"] \\\n+\t\t   \"\\n\" \"GIT_DIR: \" $GIT_DIR \\\n+\t\t   \"\\n\" \"GIT_WORK_TREE: \" $GIT_WORK_TREE \\\n+\t\t\t\"\\n\\n$err\"]\n+\t\treturn 1\n+\t}\n+\treturn 0\n+}\n+\n proc set_gitdir_vars {} {\n \tglobal _gitdir _gitworktree env\n \tif {$_gitdir ne {}} {\n-- \n2.54.0.99.14\n\n"},{"id":"543321","messageId":"20260514143322.865587-8-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 07/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:18Z","receivedAt":"2026-05-14T14:34:10Z","isPatch":true,"body":"git-gui attempts to use env(GIT_DIR) directly as the git repository,\naccepting GIT_DIR if it is a directory. Only if that fails is git\nrev-parse used to discover the repository.  But, this avoids all of\ngit-core's validity checking on a repository, thus possibly deferring an\nerror to a later step, possibly unexpected. Repository validation should\nbe part of initial setup so that later processing does not need error\ntrapping for configuration errors.\n\nLet's just invoke rev-parse so all error checking is done. Stop here if\nthe user set GIT_DIR or GIT_WORK_TREE. Otherwise, continue the existing\nbehavior and show the repository picker.\n\nAlso, remove a later check on whether _gitdir is a directory: that code\ncannot be reached without rev-parse having validating the repository.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 24 +++++++++---------------\n 1 file changed, 9 insertions(+), 15 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 2e2ddc0..81789dd 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -374,6 +374,7 @@ set _gitdir {}\n set _gitworktree {}\n set _isbare {}\n set _githtmldir {}\n+set _prefix {}\n set _reponame {}\n set _shellpath {@@SHELL_PATH@@}\n \n@@ -1167,19 +1168,18 @@ proc pick_repo {} {\n \tset picked 1\n }\n \n+# find repository.\n if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --absolute-git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n-\t} err]} {\n+\tset _gitdir [git rev-parse --absolute-git-dir]\n+} err]} {\n+\tif {[is_gitvars_error $err]} {\n+\t\texit 1\n+\t} else {\n \t\tpick_repo\n+\t}\n }\n \n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n set hashalgorithm [git rev-parse --show-object-format]\n if {$hashalgorithm eq \"sha1\"} {\n@@ -1191,12 +1191,6 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n-\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n-- \n2.54.0.99.14\n\n"},{"id":"543322","messageId":"20260514143322.865587-9-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 08/11] git-gui: simplify [is_bare] to report if a worktree is known","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:19Z","receivedAt":"2026-05-14T14:34:11Z","isPatch":true,"body":"git-gui includes proc is_bare, used in several places to make decisions\non whether a worktree exists, but also in discovery to tell if a\nworktree can be supported.\n\nBut, is_bare is out of date with regard to multiple worktrees, safe\nrepository guards, and possibly other relevant features known to git\nrev-parse. Also, is_bare caches its result on the first call, so is not\nuseful if a later step in the discovery process finds a worktree.\n\nSo, simplify is_bare to report whether git-gui has a worktree or is\nworking only from a repository.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 25 +------------------------\n 1 file changed, 1 insertion(+), 24 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 81789dd..a03eaa7 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -372,7 +372,6 @@ if {[tk windowingsystem] eq \"aqua\"} {\n set _appname {Git Gui}\n set _gitdir {}\n set _gitworktree {}\n-set _isbare {}\n set _githtmldir {}\n set _prefix {}\n set _reponame {}\n@@ -524,29 +523,7 @@ proc get_config {name} {\n }\n \n proc is_bare {} {\n-\tglobal _isbare\n-\tglobal _gitdir\n-\tglobal _gitworktree\n-\n-\tif {$_isbare eq {}} {\n-\t\tif {[catch {\n-\t\t\tset _bare [git rev-parse --is-bare-repository]\n-\t\t\tswitch  -- $_bare {\n-\t\t\ttrue { set _isbare 1 }\n-\t\t\tfalse { set _isbare 0}\n-\t\t\tdefault { throw }\n-\t\t\t}\n-\t\t}]} {\n-\t\t\tif {[is_config_true core.bare]\n-\t\t\t\t|| ($_gitworktree eq {}\n-\t\t\t\t\t&& [lindex [file split $_gitdir] end] ne {.git})} {\n-\t\t\t\tset _isbare 1\n-\t\t\t} else {\n-\t\t\t\tset _isbare 0\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn $_isbare\n+\treturn [expr {$::_gitworktree eq {}}]\n }\n \n ######################################################################\n-- \n2.54.0.99.14\n\n"},{"id":"543323","messageId":"20260514143322.865587-10-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 09/11] git-gui: support using repository parent dir as a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:20Z","receivedAt":"2026-05-14T14:34:11Z","isPatch":true,"body":"git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n2010-01-23), has had the intent to allow starting from inside a\nrepository, then switching to the parent directory if that is a valid\nworktree.\n\nThis certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n--show-toplevel without a worktree an error\", 2019-11-19) in git, but\nbreaking this git-gui feature was unintentional.\n\nAdd a proc to test if the parent of the git repository is a valid\nworktree, and set that directory as the worktree if so. Use invocations\nof git rev-parse to assure all validity and safety checks included in\ngit-core are executed.\n---\n git-gui.sh | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex a03eaa7..e326401 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1100,6 +1100,23 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc is_parent_worktree {} {\n+\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree\n+\tset ok 0\n+\tif {[file tail $::_gitdir] eq {.git}} {\n+\t\tset gitdir_parent [file join $::_gitdir {..}]\n+\t\tset expected_worktree [file normalize $gitdir_parent]\n+\t\tcatch {set git_worktree [git -C $gitdir_parent rev-parse --show-toplevel]}\n+\t\tif {[string compare $expected_worktree $git_worktree] == 0} {\n+\t\t\tset ::_prefix {}\n+\t\t\tset ::_gitworktree $git_worktree\n+\t\t\tcd $git_worktree\n+\t\t\tset ok 1\n+\t\t}\n+\t}\n+\treturn $ok\n+}\n+\n proc is_gitvars_error {err} {\n \tset havevars 0\n \tset GIT_DIR {}\n-- \n2.54.0.99.14\n\n"},{"id":"543324","messageId":"20260514143322.865587-11-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 10/11] git-gui: improve worktree discovery","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:21Z","receivedAt":"2026-05-14T14:34:13Z","isPatch":true,"body":"git gui's worktree discovery needs update based upon prior work in this\nseries. In the normal case, all information we need comes directly from\ngit rev-parse (--show-toplevel, and --show-prefix). Should this work, we\nhave a valid worktree and all git gui commands can run.\n\nIf not, we need to consider:\n- if GIT_DIR or GIT_WORK_TREE are in the environment, just stop as we\n  the input configuration was wrong, the user must fix that.\n- if we have a browser or blame subcommand, no worktree is needed so\n  git-gui can run without.\n- using the git repository's parent is a valid worktree (if possible),\n  restoring prior behavior.\n\nThe current directory should be either the root of the worktree, if one\nis found, or the top-level of the git repository.\n\nMake it so. Also, make worktree discover directly follow repository\ndiscovery, reducing the locations that might need error trapping to\ncatch configuration issues.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 56 ++++++++++++++++++++++--------------------------------\n 1 file changed, 23 insertions(+), 33 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex e326401..3a83dd5 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1173,6 +1173,28 @@ if {[catch {\n \t}\n }\n \n+# find worktree, continue without if not required\n+if {[catch {\n+\tset _gitworktree [git rev-parse --show-toplevel]\n+\tset _prefix [git rev-parse --show-prefix]\n+\tcd $_gitworktree\n+} err]} {\n+\tif {[is_gitvars_error $err]} {\n+\t\texit 1\n+\t}\n+\tset _gitworktree {}\n+\tset _prefix {}\n+\tif {[is_enabled bare]} {\n+\t\tcd $_gitdir\n+\t} elseif {![is_parent_worktree]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n+\t\texit 1\n+\t}\n+}\n+\n+# repository and worktree config are complete, export them\n+set_gitdir_vars\n \n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n set hashalgorithm [git rev-parse --show-object-format]\n@@ -1189,37 +1211,8 @@ if {$hashalgorithm eq \"sha1\"} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n \n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n-\t\texit 1\n-\t}\n-\tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n-\t}\n-\tif {[catch {cd $_gitworktree} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-}\n+# Derive a human-readable repository name\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1227,9 +1220,6 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-# Export the final paths\n-set_gitdir_vars\n-\n ######################################################################\n ##\n ## global init\n-- \n2.54.0.99.14\n\n"},{"id":"543325","messageId":"20260514143322.865587-12-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-14T14:33:22Z","receivedAt":"2026-05-14T14:34:13Z","isPatch":true,"body":"git-gui accepts subcommands blame | browser | citool, and assumes the\nsubcommand is 'gui' if none is actually given, But, git gui also has a\nrepository picker (choose_repository::pick) that can create a new\nrepository + worktree, or choose an existing one, switch to that, and\nthe run the gui. The user has no direct control over invoking the\npicker, instead the picker is triggered by failure in the repository /\nworktree discover process: this includes being started in a directory\nnot controlled by git, which is probably the intended use case.\n\nThe picker can appear when the user has no intention of creating a new\nworktree, and the user cannot use the picker to create a new worktree\ninside another.\n\nSo, add two new explicit subcommands:\n    gui  - Run the gui if repository/worktree discovery succeeds, or die\n           with an error message, but never run the picker.\n    pick - First run the picker, regardless, then start the gui in\n           the chosen worktree.\n\nNothing in this changes the prior behavior, the alternates above must be\nexplicitly selected to see any change.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 22 ++++++++++++++++++++--\n 1 file changed, 20 insertions(+), 2 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 3a83dd5..c56aeef 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1021,6 +1021,7 @@ proc load_config {include_global} {\n ##\n ## feature option selection\n \n+set run_picker_on_error 1\n if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n \tunset _junk\n } else {\n@@ -1030,6 +1031,7 @@ if {$subcommand eq {gui.sh}} {\n \tset subcommand gui\n }\n if {$subcommand eq {gui} && [llength $argv] > 0} {\n+\tset run_picker_on_error 0\n \tset subcommand [lindex $argv 0]\n \tset argv [lrange $argv 1 end]\n }\n@@ -1047,6 +1049,7 @@ blame {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tset run_picker_on_error 0\n }\n citool {\n \tenable_option singlecommit\n@@ -1055,6 +1058,7 @@ citool {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tset run_picker_on_error 0\n \n \twhile {[llength $argv] > 0} {\n \t\tset a [lindex $argv 0]\n@@ -1162,14 +1166,28 @@ proc pick_repo {} {\n \tset picked 1\n }\n \n+# run repository picker if explicitly requested\n+switch -- $subcommand {\n+\tpick {\n+\t\tpick_repo\n+\t\tset subcommand gui\n+\t\tset run_picker_on_error 0\n+\t}\n+}\n+\n # find repository.\n if {[catch {\n \tset _gitdir [git rev-parse --absolute-git-dir]\n } err]} {\n \tif {[is_gitvars_error $err]} {\n \t\texit 1\n-\t} else {\n+\t}\n+\tif {$run_picker_on_error} {\n \t\tpick_repo\n+\t} else {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n+\t\texit 1\n \t}\n }\n \n@@ -3051,7 +3069,7 @@ gui {\n \t# fall through to setup UI for commits\n }\n default {\n-\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool}\\]\"\n+\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool|gui|pick}\\]\"\n \tif {[tk windowingsystem] eq \"win32\"} {\n \t\twm withdraw .\n \t\ttk_messageBox -icon error -message $err \\\n-- \n2.54.0.99.14\n\n"},{"id":"543386","messageId":"d327da74-0949-4310-9816-e18ed01ee317@kdbg.org","threadId":"65531","inReplyTo":"tencent_2D0A6C14E8B34348B6F236BC8E7B66AB5105@qq.com","subject":"Re: [PATCH v7 1/3] git-gui: restructure repository startup","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T08:26:56Z","receivedAt":"2026-05-15T08:27:06Z","isPatch":true,"body":"Am 09.05.26 um 15:37 schrieb Shroom Moo:\n> When git-gui is started inside a .git directory of a non-bare\n> repository, it should treat the parent directory as the worktree,\n> as it did before commit 2d92ab32fd (rev-parse: make --show-toplevel\n> without a worktree an error, 2019-11-19).  However, a bare repository\n> or a separated gitdir without a worktree must be rejected early.\n> > Protect the previously unguarded calls to `git rev-parse\n> --show-object-format` and `--show-toplevel`.  Restructure the startup\n> sequence to:\n> \n> - Check for a bare repository right after loading the config.  If the\n>   repository is bare and the current subcommand does not allow bare\n>   repos (e.g. normal commit mode), show \"Cannot use bare repository\"\n>   and exit.\n> \n> - When `rev-parse --show-toplevel` fails and the repository is\n>   non-bare, the gitdir path ends with \".git\", and we are inside that\n>   gitdir, use the parent directory as the worktree.  This preserves\n>   the ability to start git-gui from within a regular repository’s\n>   .git directory, which was intentionally supported since 87cd09f43e56\n>   (git-gui: work from the .git dir, 2010-01-23).\n> \n> - Otherwise, show a descriptive error and exit.\n> \n> - Wrap `rev-parse --show-object-format` in a catch to avoid a crash\n>   when the repository configuration is broken (e.g. core.worktree\n>   pointing to an invalid path).\n> \n> Also removes the old `_prefix`‑based fallback that computed a relative\n> path to the worktree top from a subdirectory, and the unconditional\n> `[file dirname $_gitdir]` guess.  Both are unnecessary now that\n> `rev‑parse --show‑toplevel` directly provides the absolute top‑level\n> path and we can `cd` to it.  The guess is further unsafe in\n> multi‑worktree setups, where a gitdir may have more than one worktree.\n> The only remaining fallback is the explicit “.git directory” rule for\n> non‑bare repositories, which mirrors the historical behaviour.\n> Additionally, only export GIT_WORK_TREE when it is not empty, to avoid\n> confusing commands in bare-repository subcommands.\n> \n> This fixes the fatal Tcl error when the working tree is missing, while\n> keeping the .git startup feature and avoiding any automatic directory\n> switching that could be dangerous in multi‑worktree setups.\n\nI think that the end result is useful. However, frankly, the patch\nattempts to do too many things at once and should still be split further:\n\n- The removal of the cdup fallback could be a preliminary patch.\n\n- The protection of --show-object-format could be a follow-up patch.\n\n> Helped-by: Johannes Sixt <j6t@kdbg.org>\n> Helped-by: Mark Levedahl <mlevedahl@gmail.com>\n> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>\n> ---\n>  git-gui/git-gui.sh | 76 ++++++++++++++++++++++++++++++----------------\n>  1 file changed, 49 insertions(+), 27 deletions(-)\n> \n> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh\n> index 23fe76e498..9eb93a76b5 100755\n> --- a/git-gui/git-gui.sh\n> +++ b/git-gui/git-gui.sh\n> @@ -1129,7 +1129,8 @@ if {[catch {\n>  \t\t}]\n>  \t&& [catch {\n>  \t\t# beware that from the .git dir this sets _gitdir to .\n> -\t\t# and _prefix to the empty string\n> +\t\t# and _prefix to the empty string; this is handled by\n> +\t\t# the startup safety checks below\n>  \t\tset _gitdir [git rev-parse --git-dir]\n>  \t\tset _prefix [git rev-parse --show-prefix]\n>  \t} err]} {\n> @@ -1142,8 +1143,20 @@ if {[catch {\n>  \tset picked 1\n>  }\n>  \n> +if {![file isdirectory $_gitdir]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat \n> +\t\t[mc \"Git directory not found:\"] \"\\n\\n$_gitdir\\n\\n\" \\\n> +\t\t[mc \"Please ensure GIT_DIR points to a valid Git repository\"]]\n> +\texit 1\n> +}\n> +\n\nThis was moved from below. I would appreciated if there were no changes\nin the moved code so that `git diff --color-moved` can show that no\nchanges were intended. I am not sure that the additional sentence that\nmentions GIT_DIR is warranted. If you feel it is needed, please add it\nin a separate patch with a justification.\n\n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n> -set hashalgorithm [git rev-parse --show-object-format]\n> +if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat [mc \"Failed to determine hash algorithm:\"] \"\\n\\n$err\"]\n> +\texit 1\n> +}\n>  if {$hashalgorithm eq \"sha1\"} {\n>  \tset hashlength 40\n>  } elseif {$hashalgorithm eq \"sha256\"} {\n> @@ -1160,46 +1173,52 @@ if {$_gitdir eq \".\"} {\n>  \tset _gitdir [pwd]\n>  }\n>  \n> -if {![file isdirectory $_gitdir]} {\n> -\tcatch {wm withdraw .}\n> -\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n> -\texit 1\n> -}\n>  # _gitdir exists, so try loading the config\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n> -\n> -if {$_prefix ne {}} {\n> -\tif {$_gitworktree eq {}} {\n> -\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n> -\t} else {\n> -\t\tset cdup $_gitworktree\n> -\t}\n> -\tif {[catch {cd $cdup} err]} {\n> +# Handle bare repository and determine working tree\n> +if {[is_bare]} {\n> +\t# Bare repository: only allowed for certain subcommands\n> +\tif {![is_enabled bare]} {\n>  \t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n> +\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" [file normalize $_gitdir]]\n>  \t\texit 1\n>  \t}\n> -\tset _gitworktree [pwd]\n> -\tunset cdup\n> -} elseif {![is_enabled bare]} {\n> -\tif {[is_bare]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n> -\t\texit 1\n> +\t# Allowed bare repo does not have a worktree\n> +\tset _gitworktree {}\n> +} else {\n> +\t# Non-bare repository: we must find a worktree\n> +\tif {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {\n> +\t\t# The only acceptable failure is when we are inside\n> +\t\t# the .git directory of a regular repository.\n> +\t\tset inside_gitdir 0\n> +\t\tcatch {set inside_gitdir [git rev-parse --is-inside-git-dir]}\n> +\t\tif {$inside_gitdir eq {true} && [file tail $_gitdir] eq {.git}} {\n> +\t\t\t# Use the parent directory as worktree (historic behavior)\n> +\t\t\tset _gitworktree [file normalize [file dirname $_gitdir]]\n> +\t\t} else {\n> +\t\t\tcatch {wm withdraw .}\n> +\t\t\terror_popup [strcat [mc \"Cannot determine working tree:\"] \"\\n\\n$err\"]\n> +\t\t\texit 1\n> +\t\t}\n>  \t}\n> +\n>  \tif {$_gitworktree eq {}} {\n> -\t\tset _gitworktree [file dirname $_gitdir]\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [mc \"Cannot determine working tree (unexpected empty result)\"]\n> +\t\texit 1\n>  \t}\n\nAn empty $_gitworktree should be practically impossible at this point.\nPersonally, I would let the following \"cd\" handle the case (it fails if\nthe argument is empty).\n\n> +\n>  \tif {[catch {cd $_gitworktree} err]} {\n>  \t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n> +\t\terror_popup [strcat [mc \"Cannot move to working directory:\"] \"\\n\\n$err\"]\n>  \t\texit 1\n>  \t}\n>  \tset _gitworktree [pwd]\n>  }\n> +\n> +# Derive a human-readable repository name\n>  set _reponame [file split [file normalize $_gitdir]]\n>  if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end-1]\n> @@ -1207,8 +1226,11 @@ if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end]\n>  }\n>  \n> +# Export the final paths\n>  set env(GIT_DIR) $_gitdir\n> -set env(GIT_WORK_TREE) $_gitworktree\n> +if {$_gitworktree ne {}} {\n> +\tset env(GIT_WORK_TREE) $_gitworktree\n> +}\n>  \n>  ######################################################################\n>  ##\n\n-- Hannes\n\n"},{"id":"543387","messageId":"39541407-dfe6-4500-b3d4-5c8ea0cdb26d@kdbg.org","threadId":"65531","inReplyTo":"tencent_C4AD92361C8D7B76EB4C8A6F14EA33496805@qq.com","subject":"Re: [PATCH v7 3/3] git-gui: handle GIT_DIR and GIT_WORK_TREE early","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T08:28:48Z","receivedAt":"2026-05-15T08:28:51Z","isPatch":true,"body":"Am 09.05.26 um 15:37 schrieb Shroom Moo:\n> Users expect these two invocations to be equivalent:\n> \n>     GIT_WORK_TREE=/some/path GIT_DIR=/some/path/.git git gui\n>     git -C /some/path gui\n> \n> Currently, the environment variable variant often brings up the\n> repository picker or ignores the requested worktree because\n> GIT_WORK_TREE is processed too late.\n\nI cannot reproduce the case that brings the repository picker. All other\nfailure cases that I can produce are reasonable and do not indicate that\nGIT_WORK_TREE is processed too late.\n\n>  Moreover, after determining\n> the working tree, git-gui unconditionally exports GIT_WORK_TREE.\n> When no worktree is found (e.g., in a bare repository with a\n> read-only subcommand like blame), an empty value is exported, which\n> confuses commands like `git branch --show-current`.\n\nTrue. I think the culprit is that we export GIT_WORK_TREE in the first\nplace.\n\nI suggest the following patch to replace this and the previous patch.\n\n---- 8< ----\nFrom: Johannes Sixt <j6t@kdbg.org>\nSubject: [PATCH] git-gui: operate git commands without GIT_WORK_TREE\n\nThe manual page of the git command states about the --git-dir option:\n\n   Specifying the location of the \".git\" directory using this option\n   (or GIT_DIR environment variable) turns off the repository\n   discovery [...], and tells Git that you are at the top level of\n   the working tree.\n\nUse this to our advantage:\n\n- Set GIT_DIR in the environment to the value that was discovered, so\n  that the invoked git commands operate on the same repository\n  database that Git GUI uses even after it changes the working\n  directory.\n\n- After changing the working directory to the top level of the working\n  tree, ensure that GIT_WORK_TREE is not set, because, as per\n  documentation, all git invocations from then on will assume that the\n  current working directory is also the top level working tree.\n\n- Remove the now obsolete GIT_WORK_TREE dance when subordinate Gitk or\n  Git GUI are invoked for a submodule.\n\nDo keep the state of GIT_WORK_TREE if we are in a bare repository,\nbecause Git GUI is not interested in the worktree at all, as no commit\nmode is possible in a bare repository.\n\nThis avoids cases where an empty GIT_WORK_TREE was exported into the\nenvironment, most notably by a call of `git gui blame HEAD file` in a\nbare repository.\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n git-gui.sh | 24 +++++++-----------------\n 1 file changed, 7 insertions(+), 17 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 76560ec825cf..146a29a809a8 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1216,6 +1216,7 @@ if {[is_bare]} {\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n+\tcatch { unset env(GIT_WORK_TREE) }\n }\n \n # Derive a human-readable repository name\n@@ -1228,9 +1229,6 @@ if {[lindex $_reponame end] eq {.git}} {\n \n # Export the final paths\n set env(GIT_DIR) $_gitdir\n-if {$_gitworktree ne {}} {\n-\tset env(GIT_WORK_TREE) $_gitworktree\n-}\n \n ######################################################################\n ##\n@@ -2029,7 +2027,7 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitdir _gitworktree\n+\tglobal _gitdir\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n@@ -2043,11 +2041,7 @@ proc do_gitk {revs {is_submodule false}} {\n \n \t\tset pwd [pwd]\n \n-\t\tif {!$is_submodule} {\n-\t\t\tif {![is_bare]} {\n-\t\t\t\tcd $_gitworktree\n-\t\t\t}\n-\t\t} else {\n+\t\tif {$is_submodule} {\n \t\t\tcd $current_diff_path\n \t\t\tif {$revs eq {--}} {\n \t\t\t\tset s $file_states($current_diff_path)\n@@ -2067,18 +2061,16 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t\t}\n \t\t\t\tset revs $old_sha1...$new_sha1\n \t\t\t}\n-\t\t\t# GIT_DIR and GIT_WORK_TREE for the submodule are not the ones\n-\t\t\t# we've been using for the main repository, so unset them.\n+\t\t\t# GIT_DIR for the submodule is not the one we've been using for\n+\t\t\t# the main repository, so unset it. (GIT_WORK_TREE is already unset.)\n \t\t\t# TODO we could make life easier (start up faster?) for gitk\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n \t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2102,12 +2094,11 @@ proc do_git_gui {} {\n \t\terror_popup [mc \"Couldn't find git gui in PATH\"]\n \t} else {\n \t\tglobal env\n-\t\tglobal _gitdir _gitworktree\n+\t\tglobal _gitdir\n \n-\t\t# see note in do_gitk about unsetting these vars when\n+\t\t# see note in do_gitk about unsetting this variable when\n \t\t# running tools in a submodule\n \t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n@@ -2115,7 +2106,6 @@ proc do_git_gui {} {\n \t\tsafe_exec_bg [concat $exe gui]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.54.0.215.g4fe990ec16\n\n"},{"id":"543390","messageId":"f02e354d-a182-4207-8723-e5261e19351b@kdbg.org","threadId":"65531","inReplyTo":"tencent_A6BA86DF71476C6948398C167C0E0919550A@qq.com","subject":"Re: [PATCH v7 2/3] git-gui: disable gitk visualization when no worktree available","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T08:28:18Z","receivedAt":"2026-05-15T09:11:44Z","isPatch":true,"body":"Am 09.05.26 um 15:37 schrieb Shroom Moo:\n> When git-gui is started in a bare repository with the 'bare' option\n> enabled (e.g., for blame/browser), there is no working tree.  The\n> \"Visualize Current Branch's History\" and \"Visualize All Branch\n> History\" menu items remain enabled, but clicking them triggers a Tcl\n> error because do_gitk tries to change directory to an empty\n> _gitworktree.\n\nI cannot reproduce this claim. The failure is not a Tcl error, but an\nerror in some `git` invocation that cannot handle an empty\nGIT_WORK_TREE. And that happens only beginning with the *second*\ninvocation of one of the \"Visualize\" calls, because then an empty\nGIT_WORK_TREE is exported into the environment.\n\n> \n> Fix this by disabling the two visualization menu items when the\n> repository is bare and the 'bare' option is active.  Also update\n> current_branch_write to keep the state consistent when the branch\n> changes, and add a defensive check in do_gitk to avoid the error\n> should the menu state somehow become out of sync.\n\nThis change is not correct. Gitk can operate without a working tree. The\nmenu entries should not be disabled, ever. The bug is somewhere else.\n\nSee also my suggested replacement patch in my reply to 3/3.\n\n-- Hannes\n\n"},{"id":"543395","messageId":"20260515110027.426-1-bootaina702@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-5-mlevedahl@gmail.com","subject":"Re: [PATCH v1 04/11] git-gui: put choose_repository::pick in a proc","fromName":"Aina Boot","fromEmail":"bootaina702@gmail.com","sentAt":"2026-05-15T11:00:22Z","receivedAt":"2026-05-15T11:03:01Z","isPatch":true,"body":"On 5/14/26 2:33 PM, Mark Levedahl wrote:\n>  set picked 0\n> +proc pick_repo {} {\n> +\tunset_gitdir_vars\n> +\tload_config 1\n> +\tapply_config\n> +\tchoose_repository::pick\n> +\tset _gitdir [git rev-parse --absolute-git-dir]\n> +\tset _prefix {}\n> +\tset picked 1\n> +}\n> +\n>\n\nHere inside the proc it create vars locally, \"global...\" is missing.\n\nAina\n"},{"id":"543400","messageId":"6483a7a6-b1fe-4047-8c19-ab14a3284e0c@gmail.com","threadId":"65531","inReplyTo":"20260515110027.426-1-bootaina702@gmail.com","subject":"Re: [PATCH v1 04/11] git-gui: put choose_repository::pick in a proc","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-15T13:33:58Z","receivedAt":"2026-05-15T13:34:01Z","isPatch":true,"body":"\n\nOn 5/15/26 7:00 AM, Aina Boot wrote:\n> On 5/14/26 2:33 PM, Mark Levedahl wrote:\n>>  set picked 0\n>> +proc pick_repo {} {\n>> +\tunset_gitdir_vars\n>> +\tload_config 1\n>> +\tapply_config\n>> +\tchoose_repository::pick\n>> +\tset _gitdir [git rev-parse --absolute-git-dir]\n>> +\tset _prefix {}\n>> +\tset picked 1\n>> +}\n>> +\n>>\n> Here inside the proc it create vars locally, \"global...\" is missing.\n>\n> Aina\nYes, also, I missed copying the check on the return variable ::_gitdir. That proc should be:\n\nproc pick_repo {} {\n    global _gitdir picked\n    unset_gitdir_vars\n    load_config 1\n    apply_config\n    choose_repository::pick\n    if {![file isdirectory $_gitdir]} {\n        exit 1\n    }\n    set _gitdir [git rev-parse --absolute-git-dir]\n    set picked 1\n}\n\nMark\n"},{"id":"543409","messageId":"796217c3-8998-47a8-9a46-298541708d41@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-2-mlevedahl@gmail.com","subject":"Re: [PATCH v1 01/11] git-gui: allow specifying path '.' to the browser","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T15:54:25Z","receivedAt":"2026-05-15T15:54:38Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> Invoking \"git-gui browser rev .\" should show the file browser for the\n> commitish rev, starting at the root directory. This errors out in\n> normalize_relpath because the '.' is removed, yielding an empty list as\n> argument to [file join ...]. Fix this.\n\nGood catch!\n\nThe description isn't precise, though. '.' means to list the current\ndirectory. The mentioned problem happens only if this is also the root\nof the working tree.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 6 +++++-\n>  1 file changed, 5 insertions(+), 1 deletion(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 23fe76e..6048f92 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -2965,7 +2965,11 @@ proc normalize_relpath {path} {\n>  \t\t}\n>  \t\tlappend elements $item\n>  \t}\n> -\treturn [eval file join $elements]\n> +\tif {$elements ne {}} {\n> +\t\treturn [eval file join $elements]\n> +\t} else {\n> +\t\treturn {}\n> +\t}\n>  }\n>  \n>  # -- Not a normal commit type invocation?  Do that instead!\n\n\n-- Hannes\n\n"},{"id":"543410","messageId":"cb3012ab-1a97-4197-bc57-34eb3fa472a2@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-3-mlevedahl@gmail.com","subject":"Re: [PATCH v1 02/11] git-gui: refactor browser / blame argument parsing","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T15:56:08Z","receivedAt":"2026-05-15T15:56:11Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui has subcommands blame and browser, both of which accept a\n> pathname, possibly preceded by a commit-ish item to specify a revision.\n> Also, blame can take a first argument that gives a line number to focus.\n> \n> The command line parser for the above is more complex than needed, and\n> cannot work without a worktree as the pathname objects are checked\n> against the current worktree for existence. This also precludes naming a\n> directory or file that does not exist on the currently checked out\n> branch.\n\nWhile the old browser isn't simple, it implements the strategy \"revs\nbefore paths, no revs after the first path or '--'\" that is applied by\nevery git command. The rewritten parser is only slightly simpler. It\nshould not ignore \"--\". Furthermore, the old parser ignored excessive\ntrailing arguments, while the new parser ignores excessive leading\narguments. Neither is desirable, and we should report an incorrect\nargument list (if we don't, then I prefer the old behavior).\n\n> \n> So, replace this with a simpler parser that looks at argument number and\n> number of arguments to know what value to expect. The blame and browser\n> backends already have error checking with diagnostic information, so\n> defer most error checking to those. Also, allow a line-number selection\n> to be given and silently ignored for the browser, further simplifying\n> this code.\n\nThe line number selection isn't ignored by the browser, but reported as\nan incorrect usage before and after this patch.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 66 +++++++++++++-----------------------------------------\n>  1 file changed, 16 insertions(+), 50 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 6048f92..a951fcd 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -2986,51 +2986,34 @@ blame {\n>  \tset head {}\n>  \tset path {}\n>  \tset jump_spec {}\n> -\tset is_path 0\n> +\tset nargs [llength $argv]\n> +\tif {$nargs < 1} {\n> +\t\tusage\n> +\t}\n> +\tset argn 0\n>  \tforeach a $argv {\n> -\t\tset p [file join $_prefix $a]\n> +\t\tset argn [expr {$argn + 1}]\n>  \n> -\t\tif {$is_path || [file exists $p]} {\n> -\t\t\tif {$path ne {}} usage\n> -\t\t\tset path [normalize_relpath $p]\n> -\t\t\tbreak\n> -\t\t} elseif {$a eq {--}} {\n> -\t\t\tif {$path ne {}} {\n> -\t\t\t\tif {$head ne {}} usage\n> -\t\t\t\tset head $path\n> -\t\t\t\tset path {}\n> +\t\tif {$argn < $nargs} {\n> +\t\t\t# revision or line number\n> +\t\t\tif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n> +\t\t\t\tset jump_spec [list $lnum]\n> +\t\t\t} else {\n> +\t\t\t\tset head $a\n>  \t\t\t}\n> -\t\t\tset is_path 1\n> -\t\t} elseif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n> -\t\t\tif {$jump_spec ne {} || $head ne {}} usage\n> -\t\t\tset jump_spec [list $lnum]\n> -\t\t} elseif {$head eq {}} {\n> -\t\t\tif {$head ne {}} usage\n> -\t\t\tset head $a\n> -\t\t\tset is_path 1\n> -\t\t} else {\n> -\t\t\tusage\n> -\t\t}\n> -\t}\n> -\tunset is_path\n> -\n> -\tif {$head ne {} && $path eq {}} {\n> -\t\tif {[string index $head 0] eq {/}} {\n> -\t\t\tset path [normalize_relpath $head]\n> -\t\t\tset head {}\n>  \t\t} else {\n> -\t\t\tset path [normalize_relpath $_prefix$head]\n> -\t\t\tset head {}\n> +\t\t\tset path [normalize_relpath $a]\n\nThis loses the capability to request a browser relative to a\nsubdirectory of the working tree. For example, \"git gui browser main .\"\nnow shows the top-level directory instead of the subdirectory when\ninvoked from a subdirectory.\n\n>  \t\t}\n>  \t}\n>  \n>  \tif {$head eq {}} {\n>  \t\tload_current_branch\n> +\t\tset head $current_branch\n>  \t} else {\n>  \t\tif {[regexp [string map \"@@ [expr $hashlength - 1]\" {^[0-9a-f]{1,@@}$}] $head]} {\n>  \t\t\tif {[catch {\n> -\t\t\t\t\tset head [git rev-parse --verify $head]\n> -\t\t\t\t} err]} {\n> +\t\t\t\tset head [git rev-parse --verify $head]\n> +\t\t\t} err]} {\n\nPlease leave the indentation unchanged.\n\n>  \t\t\t\tif {[tk windowingsystem] eq \"win32\"} {\n>  \t\t\t\t\ttk_messageBox -icon error -title [mc Error] -message $err\n>  \t\t\t\t} else {\n> @@ -3046,26 +3029,9 @@ blame {\n>  \tswitch -- $subcommand {\n>  \tbrowser {\n>  \t\tif {$jump_spec ne {}} usage\n> -\t\tif {$head eq {}} {\n> -\t\t\tif {$path ne {} && [file isdirectory $path]} {\n> -\t\t\t\tset head $current_branch\n> -\t\t\t} else {\n> -\t\t\t\tset head $path\n> -\t\t\t\tset path {}\n> -\t\t\t}\n> -\t\t}\n>  \t\tbrowser::new $head $path\n>  \t}\n>  \tblame   {\n> -\t\tif {$head eq {} && ![file exists $path]} {\n> -\t\t\tcatch {wm withdraw .}\n> -\t\t\ttk_messageBox \\\n> -\t\t\t\t-icon error \\\n> -\t\t\t\t-type ok \\\n> -\t\t\t\t-title [mc \"git-gui: fatal error\"] \\\n> -\t\t\t\t-message [mc \"fatal: cannot stat path %s: No such file or directory\" $path]\n> -\t\t\texit 1\n> -\t\t}\n>  \t\tblame::new $head $path $jump_spec\n>  \t}\n>  \t}\n\nThe check for the existence of files is actually necessary to\ndisambiguate the meaning of the argument. If a file \"maint\" exists, then\nthe argument is to be interpreted as path, not as the ref \"maint\", even\nif that exists, too.\n\nI suggest to protect the \"file exists\" calls with ($_gitworktree ne {}\n&& ...) or (![is_bare] && ...) to handle being invoked from a bare\nrepository. That is, in a bare repository we treat arguments the same as\nfiles that do not exist in the currently checked-out branch.\n\n-- Hannes\n\n"},{"id":"543411","messageId":"d9af8b60-e354-4cfc-87b3-a3e708b362da@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-4-mlevedahl@gmail.com","subject":"Re: [PATCH v1 03/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T15:58:56Z","receivedAt":"2026-05-15T15:58:59Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui unconditionally exports GIT_DIR and GIT_WORK_TREE to the\n> environment, and furthmore unconditionally unsets these in many places.\n> But, GIT_WORK_TREE should be set only if it is not {} as the empty\n> value, really meaning no work-tree is found, causes git to throw fatal\n> errors (git-gui gets the error from branch --show-current).  Fixing this\n> is required to allow blame and browser to operate from a repository\n> without a worktree.\n> \n> Establish a pair of functions to remove GIT_DIR and GIT_WORK_TREE from\n> the environment, avoiding any error if they do not exist. Also, add a\n> function to export these, but export GIT_WORK_TREE only if not empty.\n\nGood. But as I said in a parallel thread, I actually concur with your\nassessment in the coverletter of this patch series that GIT_WORK_TREE\nshould be not set at all. At least in the modes that require a working tree.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 32 ++++++++++++++++++++++----------\n>  1 file changed, 22 insertions(+), 10 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index a951fcd..387cad6 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1122,6 +1122,22 @@ unset argv0dir\n>  ##\n>  ## repository setup\n>  \n> +proc set_gitdir_vars {} {\n> +\tglobal _gitdir _gitworktree env\n> +\tif {$_gitdir ne {}} {\n> +\t\tset env(GIT_DIR) $_gitdir\n> +\t}\n> +\tif {$_gitworktree ne {}} {\n> +\t\tset env(GIT_WORK_TREE) $_gitworktree\n> +\t}\n> +}\n> +\n> +proc unset_gitdir_vars {} {\n> +\tglobal env\n> +\tcatch {unset env(GIT_DIR)}\n> +\tcatch {unset env(GIT_WORK_TREE)}\n> +}\n> +\n>  set picked 0\n>  if {[catch {\n>  \t\tset _gitdir $env(GIT_DIR)\n> @@ -1207,8 +1223,8 @@ if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end]\n>  }\n>  \n> -set env(GIT_DIR) $_gitdir\n> -set env(GIT_WORK_TREE) $_gitworktree\n> +# Export the final paths\n> +set_gitdir_vars\n>  \n>  ######################################################################\n>  ##\n> @@ -2050,13 +2066,11 @@ proc do_gitk {revs {is_submodule false}} {\n>  \t\t\t# TODO we could make life easier (start up faster?) for gitk\n>  \t\t\t# by setting these to the appropriate values to allow gitk\n>  \t\t\t# to skip the heuristics to find their proper value\n> -\t\t\tunset env(GIT_DIR)\n> -\t\t\tunset env(GIT_WORK_TREE)\n> +\t\t\tunset_gitdir_vars\n>  \t\t}\n>  \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n>  \n> -\t\tset env(GIT_DIR) $_gitdir\n> -\t\tset env(GIT_WORK_TREE) $_gitworktree\n> +\t\tset_gitdir_vars\n>  \t\tcd $pwd\n>  \n>  \t\tif {[info exists main_status]} {\n> @@ -2084,16 +2098,14 @@ proc do_git_gui {} {\n>  \n>  \t\t# see note in do_gitk about unsetting these vars when\n>  \t\t# running tools in a submodule\n> -\t\tunset env(GIT_DIR)\n> -\t\tunset env(GIT_WORK_TREE)\n> +\t\tunset_gitdir_vars\n>  \n>  \t\tset pwd [pwd]\n>  \t\tcd $current_diff_path\n>  \n>  \t\tsafe_exec_bg [concat $exe gui]\n>  \n> -\t\tset env(GIT_DIR) $_gitdir\n> -\t\tset env(GIT_WORK_TREE) $_gitworktree\n> +\t\tset_gitdir_vars\n>  \t\tcd $pwd\n>  \n>  \t\tset status_operation [$::main_status \\\n\nAfter these changes, a 'global env' probably becomes stale and could be\nremoved.\n\n-- Hannes\n\n"},{"id":"543412","messageId":"7544deeb-163c-4444-833a-7b840a7caa4a@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-5-mlevedahl@gmail.com","subject":"Re: [PATCH v1 04/11] git-gui: put choose_repository::pick in a proc","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T15:59:37Z","receivedAt":"2026-05-15T15:59:41Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui includes a 'repository picker', which allows creating a new\n> repository + worktree, or selecting a worktree from a recent list.\n> git-gui runs the picker when a valid git repository is not found. All of\n> the code for this is embedded in the discovery process block, making the\n> latter more difficult to read, and also making things more difficult if\n> we want to have an explicit 'pick' subcommand to force this to run.\n\nOK, let's see how useful this becomes.\n\n> \n> Let's move this invocation and supporting code to a separate proc,\n> aiding in subsequent refactoring. Assure GIT_DIR and GIT_WORK_TREE are\n> unset, configuration is loaded, ant that _gitdir is correctly set\n\ns/ant/and/\n\n> afterwards. As this is invoked before worktree discovery, later code\n> will set that anyway so need not be included here.\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 18 +++++++++++-------\n>  1 file changed, 11 insertions(+), 7 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 387cad6..0b73c35 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1139,6 +1139,16 @@ proc unset_gitdir_vars {} {\n>  }\n>  \n>  set picked 0\n> +proc pick_repo {} {\n> +\tunset_gitdir_vars\n> +\tload_config 1\n> +\tapply_config\n> +\tchoose_repository::pick\n> +\tset _gitdir [git rev-parse --absolute-git-dir]\n> +\tset _prefix {}\n> +\tset picked 1\n> +}\n> +\n\nSo, this isn't intended as a plain move of code? Since we set _gitdir\nhere, we could remove the corresonding lines from lib/choose_repository.tcl.\n\nIs the variable \"picked\" only needed for this particular picker\ninvocation? Then it should not be set in the function, but at the call site.\n\n>  if {[catch {\n>  \t\tset _gitdir $env(GIT_DIR)\n>  \t\tset _prefix {}\n> @@ -1149,13 +1159,7 @@ if {[catch {\n>  \t\tset _gitdir [git rev-parse --git-dir]\n>  \t\tset _prefix [git rev-parse --show-prefix]\n>  \t} err]} {\n> -\tload_config 1\n> -\tapply_config\n> -\tchoose_repository::pick\n> -\tif {![file isdirectory $_gitdir]} {\n> -\t\texit 1\n> -\t}\n> -\tset picked 1\n> +\t\tpick_repo\n\nThe indentation is off here.\n\n>  }\n>  \n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n\n-- Hannes\n\n"},{"id":"543413","messageId":"948f9f9f-8225-4bfe-be7d-e9b03c912aeb@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-6-mlevedahl@gmail.com","subject":"Re: [PATCH v1 05/11] git-gui: use --absolute-git-dir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T16:00:51Z","receivedAt":"2026-05-15T16:00:55Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui uses git rev-parse --git-dir to get the pathname of the\n> discovered git repository. The returned value can be relative, and is\n> '.' if the current directory is the top of the repository directory\n> itself.  git-gui has code to change '.' to [pwd] in this case so that\n> subsequent logic runs.\n> \n> But, git rev-parse supports --absolute-git-dir from fac60b8925\n> (\"rev-parse: add option for absolute or relative path formatting\",\n> 2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\n> this more useful form is always available. Use --absolute-git-dir to\n> always get an absolute path, avoiding the need for other checks.\n\nNice!\n\nHowever, the patch is incomplete. We set _gitdir also from\nlib/choose_repository.tcl. I think it would be best to swap this patch\nwith patch 4/11, remove the _gitdir setters from the picker\nimplementation, and call `rev-parse --absolute-git-dir` like you did in\n4/11. This depends on that the picker sets the current directory to the\ntop-level of the working tree with the embeded .git directory.\n\nBTW, missing sign-off.\n\n> ---\n>  git-gui.sh | 10 ++--------\n>  1 file changed, 2 insertions(+), 8 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 0b73c35..c2cf5f1 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1156,7 +1156,7 @@ if {[catch {\n>  \t&& [catch {\n>  \t\t# beware that from the .git dir this sets _gitdir to .\n>  \t\t# and _prefix to the empty string\n> -\t\tset _gitdir [git rev-parse --git-dir]\n> +\t\tset _gitdir [git rev-parse --absolute-git-dir]\n>  \t\tset _prefix [git rev-parse --show-prefix]\n>  \t} err]} {\n>  \t\tpick_repo\n> @@ -1173,18 +1173,12 @@ if {$hashalgorithm eq \"sha1\"} {\n>  \texit 1\n>  }\n>  \n> -# we expand the _gitdir when it's just a single dot (i.e. when we're being\n> -# run from the .git dir itself) lest the routines to find the worktree\n> -# get confused\n> -if {$_gitdir eq \".\"} {\n> -\tset _gitdir [pwd]\n> -}\n> -\n>  if {![file isdirectory $_gitdir]} {\n>  \tcatch {wm withdraw .}\n>  \terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n>  \texit 1\n>  }\n> +\n>  # _gitdir exists, so try loading the config\n>  load_config 0\n>  apply_config\n\n-- Hannes\n\n"},{"id":"543414","messageId":"d8844726-0b08-4035-946e-c5ada0759f32@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-8-mlevedahl@gmail.com","subject":"Re: [PATCH v1 07/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-15T16:06:03Z","receivedAt":"2026-05-15T16:06:07Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui attempts to use env(GIT_DIR) directly as the git repository,\n> accepting GIT_DIR if it is a directory. Only if that fails is git\n> rev-parse used to discover the repository.  But, this avoids all of\n> git-core's validity checking on a repository, thus possibly deferring an\n> error to a later step, possibly unexpected. Repository validation should\n> be part of initial setup so that later processing does not need error\n> trapping for configuration errors.\n\nOK. If the user gave us GIT_DIR with our without GIT_WORK_TREE, then\nthat combination better be workable.\n\n> \n> Let's just invoke rev-parse so all error checking is done. Stop here if\n> the user set GIT_DIR or GIT_WORK_TREE. Otherwise, continue the existing\n> behavior and show the repository picker.\n\nOK. But the paragraph is confusing, because a big \"If an error occurs\"\nis missing after the first sentence.\n\n> \n> Also, remove a later check on whether _gitdir is a directory: that code\n> cannot be reached without rev-parse having validating the repository.\n\nGood.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 24 +++++++++---------------\n>  1 file changed, 9 insertions(+), 15 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 2e2ddc0..81789dd 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -374,6 +374,7 @@ set _gitdir {}\n>  set _gitworktree {}\n>  set _isbare {}\n>  set _githtmldir {}\n> +set _prefix {}\n>  set _reponame {}\n>  set _shellpath {@@SHELL_PATH@@}\n>  \n> @@ -1167,19 +1168,18 @@ proc pick_repo {} {\n>  \tset picked 1\n>  }\n>  \n> +# find repository.\n>  if {[catch {\n> -\t\tset _gitdir $env(GIT_DIR)\n> -\t\tset _prefix {}\n> -\t\t}]\n> -\t&& [catch {\n> -\t\t# beware that from the .git dir this sets _gitdir to .\n> -\t\t# and _prefix to the empty string\n> -\t\tset _gitdir [git rev-parse --absolute-git-dir]\n> -\t\tset _prefix [git rev-parse --show-prefix]\n> -\t} err]} {\n> +\tset _gitdir [git rev-parse --absolute-git-dir]\n\nPlease do also set _prefix. It should fix the bug that the file chooser\nuses an empty prefix after\n\ncd lib\nGIT_DIR=$PWD/../.git GIT_WORK_TREE=$PWD/.. ../git-gui.sh browser master .\n\n(this is an old bug.)\n\nPlease keep the additional indentation of the catch body.\n\n> +} err]} {\n> +\tif {[is_gitvars_error $err]} {\n> +\t\texit 1\n> +\t} else {\n>  \t\tpick_repo\n> +\t}\n\nTreat the 'if' as an early exist without an else, and we don't need the\npreviously strange indentation of 'pick_repo'.\n\n>  }\n>  \n> +\n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>  set hashalgorithm [git rev-parse --show-object-format]\n>  if {$hashalgorithm eq \"sha1\"} {\n> @@ -1191,12 +1191,6 @@ if {$hashalgorithm eq \"sha1\"} {\n>  \texit 1\n>  }\n>  \n> -if {![file isdirectory $_gitdir]} {\n> -\tcatch {wm withdraw .}\n> -\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n> -\texit 1\n> -}\n> -\n>  # _gitdir exists, so try loading the config\n>  load_config 0\n>  apply_config\n\n(Stopping the review here for today.)\n\n-- Hannes\n\n"},{"id":"543439","messageId":"267ad9f6-7816-41ba-8981-bc0e4286577c@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-9-mlevedahl@gmail.com","subject":"Re: [PATCH v1 08/11] git-gui: simplify [is_bare] to report if a worktree is known","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-16T08:12:45Z","receivedAt":"2026-05-16T08:12:50Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui includes proc is_bare, used in several places to make decisions\n> on whether a worktree exists, but also in discovery to tell if a\n> worktree can be supported.\n> \n> But, is_bare is out of date with regard to multiple worktrees, safe\n> repository guards, and possibly other relevant features known to git\n> rev-parse. Also, is_bare caches its result on the first call, so is not\n> useful if a later step in the discovery process finds a worktree.\n> \n> So, simplify is_bare to report whether git-gui has a worktree or is\n> working only from a repository.\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 25 +------------------------\n>  1 file changed, 1 insertion(+), 24 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 81789dd..a03eaa7 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -372,7 +372,6 @@ if {[tk windowingsystem] eq \"aqua\"} {\n>  set _appname {Git Gui}\n>  set _gitdir {}\n>  set _gitworktree {}\n> -set _isbare {}\n>  set _githtmldir {}\n>  set _prefix {}\n>  set _reponame {}\n> @@ -524,29 +523,7 @@ proc get_config {name} {\n>  }\n>  \n>  proc is_bare {} {\n> -\tglobal _isbare\n> -\tglobal _gitdir\n> -\tglobal _gitworktree\n> -\n> -\tif {$_isbare eq {}} {\n> -\t\tif {[catch {\n> -\t\t\tset _bare [git rev-parse --is-bare-repository]\n> -\t\t\tswitch  -- $_bare {\n> -\t\t\ttrue { set _isbare 1 }\n> -\t\t\tfalse { set _isbare 0}\n> -\t\t\tdefault { throw }\n> -\t\t\t}\n> -\t\t}]} {\n> -\t\t\tif {[is_config_true core.bare]\n> -\t\t\t\t|| ($_gitworktree eq {}\n> -\t\t\t\t\t&& [lindex [file split $_gitdir] end] ne {.git})} {\n> -\t\t\t\tset _isbare 1\n> -\t\t\t} else {\n> -\t\t\t\tset _isbare 0\n> -\t\t\t}\n> -\t\t}\n> -\t}\n> -\treturn $_isbare\n> +\treturn [expr {$::_gitworktree eq {}}]\n>  }\n>  \n>  ######################################################################\n\nVery nice!\n\nIMO, regardless of which way we end up rewriting repository discovery,\nthe end result should be that we can use $_gitworktree like this to tell\nwhether we are in a bare repository or not.\n\n-- Hannes\n\n"},{"id":"543440","messageId":"4d25544d-1a7e-4407-9191-1fb05ff55244@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-10-mlevedahl@gmail.com","subject":"Re: [PATCH v1 09/11] git-gui: support using repository parent dir as a worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-16T08:14:57Z","receivedAt":"2026-05-16T08:15:01Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n> 2010-01-23), has had the intent to allow starting from inside a\n> repository, then switching to the parent directory if that is a valid\n> worktree.\n> \n> This certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n> --show-toplevel without a worktree an error\", 2019-11-19) in git, but\n> breaking this git-gui feature was unintentional.\n> \n> Add a proc to test if the parent of the git repository is a valid\n> worktree, and set that directory as the worktree if so. Use invocations\n> of git rev-parse to assure all validity and safety checks included in\n> git-core are executed.\n\nBTW, missing sign-off.\n\n> ---\n>  git-gui.sh | 17 +++++++++++++++++\n>  1 file changed, 17 insertions(+)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index a03eaa7..e326401 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1100,6 +1100,23 @@ unset argv0dir\n>  ##\n>  ## repository setup\n>  \n> +proc is_parent_worktree {} {\n> +\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree\n> +\tset ok 0\n> +\tif {[file tail $::_gitdir] eq {.git}} {\n> +\t\tset gitdir_parent [file join $::_gitdir {..}]\n> +\t\tset expected_worktree [file normalize $gitdir_parent]\n\nWe have [file dirname ...]. Is there a reason to avoid it?\n\n> +\t\tcatch {set git_worktree [git -C $gitdir_parent rev-parse --show-toplevel]}\n> +\t\tif {[string compare $expected_worktree $git_worktree] == 0} {\n\nThe purpose of this check should be explained in a comment. I think it is:\n\nFor a repository with the database in a directory named .git we assume\nthat the working tree is the directory containing .git. But\nconfiguration may point to a different worktree. Then we do not want to\nhold on to our assumption.\n\nHowever, whether [git -C elsewhere ...] uses the same gitdir that we\nhave discovered so far cannot be told from this piece of code alone.\nTherefore, I think it is wrong to extract this check into a function.\n\nAlso, I don't think we can use string comparison here. On Windows, the\ncommand returns the Windows style path, but Tcl my operate with a POSIX\nstyle path.\n\n> +\t\t\tset ::_prefix {}\n> +\t\t\tset ::_gitworktree $git_worktree\n> +\t\t\tcd $git_worktree\n\nSo many side-effects in a function whose name suggests that it only does\nsome checks. Please, don't do that.\n\n> +\t\t\tset ok 1\n> +\t\t}\n> +\t}\n> +\treturn $ok\n> +}\n> +\n>  proc is_gitvars_error {err} {\n>  \tset havevars 0\n>  \tset GIT_DIR {}\n\nIn general, I am not a fan of commits that add new functions, but no\ncall sites. Please squash this into 10/11. Ditto for is_gitvars_error in\n06/11.\n\n-- Hannes\n\n"},{"id":"543441","messageId":"8b8feffa-1651-41aa-ac76-d2721d656b45@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-11-mlevedahl@gmail.com","subject":"Re: [PATCH v1 10/11] git-gui: improve worktree discovery","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-16T08:16:26Z","receivedAt":"2026-05-16T08:16:29Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git gui's worktree discovery needs update based upon prior work in this\n> series. In the normal case, all information we need comes directly from\n> git rev-parse (--show-toplevel, and --show-prefix). Should this work, we\n> have a valid worktree and all git gui commands can run.\n> \n> If not, we need to consider:\n> - if GIT_DIR or GIT_WORK_TREE are in the environment, just stop as we\n>   the input configuration was wrong, the user must fix that.\n> - if we have a browser or blame subcommand, no worktree is needed so\n>   git-gui can run without.\n> - using the git repository's parent is a valid worktree (if possible),\n>   restoring prior behavior.\n> \n> The current directory should be either the root of the worktree, if one\n> is found, or the top-level of the git repository.\n\nI disagree in the case where no working tree is found. Then there is no\npoint in changing the current directory.\n\n> \n> Make it so. Also, make worktree discover directly follow repository\n> discovery, reducing the locations that might need error trapping to\n> catch configuration issues.\n\nGood!\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n\nBTW, please make the subject line more descriptive. The word \"improve\"\ndoes not convey anything of importance.\n\n> ---\n>  git-gui.sh | 56 ++++++++++++++++++++++--------------------------------\n>  1 file changed, 23 insertions(+), 33 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index e326401..3a83dd5 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1173,6 +1173,28 @@ if {[catch {\n>  \t}\n>  }\n>  \n> +# find worktree, continue without if not required\n> +if {[catch {\n> +\tset _gitworktree [git rev-parse --show-toplevel]\n> +\tset _prefix [git rev-parse --show-prefix]\n> +\tcd $_gitworktree\n> +} err]} {\n\nWe have three commands, each with their own possible failure sources.\nOne of the outcomes of an error is that we proceed anyway. I think that\nthis is incorrect if the 'cd' fails: we must not proceed if it fails.\nTherefore, we must handle its failure separately.\n\n> +\tif {[is_gitvars_error $err]} {\n> +\t\texit 1\n> +\t}\n> +\tset _gitworktree {}\n> +\tset _prefix {}\n> +\tif {[is_enabled bare]} {\n> +\t\tcd $_gitdir\n\nWhy change the directory here? If we run `git gui browser master dir` we\ndo not want to change the directory in an uncontrolled manner. The\nargument parser will want to check for the existence of files, and then\nwe do not want to operate from a random directory.\n\nAlso, I think that the check must be for [is_bare] and not [is_enabled\nbare].\n\n> +\t} elseif {![is_parent_worktree]} {\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n> +\t\texit 1\n> +\t}\n> +}\n> +\n> +# repository and worktree config are complete, export them\n> +set_gitdir_vars\n>  \n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>  set hashalgorithm [git rev-parse --show-object-format]\n\nThis moves code around. In particular, we see load_config and\napply_config in the context below, which now happens only after these\ncalls. How certain are we that these have no effect on the code that\nruns now earlier?\n\n> @@ -1189,37 +1211,8 @@ if {$hashalgorithm eq \"sha1\"} {\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n>  \n> -if {$_prefix ne {}} {\n> -\tif {$_gitworktree eq {}} {\n> -\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n> -\t} else {\n> -\t\tset cdup $_gitworktree\n> -\t}\n> -\tif {[catch {cd $cdup} err]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n> -\t\texit 1\n> -\t}\n> -\tset _gitworktree [pwd]\n> -\tunset cdup\n> -} elseif {![is_enabled bare]} {\n> -\tif {[is_bare]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n> -\t\texit 1\n> -\t}\n> -\tif {$_gitworktree eq {}} {\n> -\t\tset _gitworktree [file dirname $_gitdir]\n> -\t}\n> -\tif {[catch {cd $_gitworktree} err]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n> -\t\texit 1\n> -\t}\n> -\tset _gitworktree [pwd]\n> -}\n> +# Derive a human-readable repository name\n>  set _reponame [file split [file normalize $_gitdir]]\n>  if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end-1]\n> @@ -1227,9 +1220,6 @@ if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end]\n>  }\n>  \n> -# Export the final paths\n> -set_gitdir_vars\n> -\n>  ######################################################################\n>  ##\n>  ## global init\n\n-- Hannes\n\n"},{"id":"543442","messageId":"e336544b-941d-43ed-890f-2b8950dbaf88@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-12-mlevedahl@gmail.com","subject":"Re: [PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-16T08:18:20Z","receivedAt":"2026-05-16T08:18:24Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git-gui accepts subcommands blame | browser | citool, and assumes the\n> subcommand is 'gui' if none is actually given, But, git gui also has a\n> repository picker (choose_repository::pick) that can create a new\n> repository + worktree, or choose an existing one, switch to that, and\n> the run the gui. The user has no direct control over invoking the\n> picker, instead the picker is triggered by failure in the repository /\n> worktree discover process: this includes being started in a directory\n> not controlled by git, which is probably the intended use case.\n> \n> The picker can appear when the user has no intention of creating a new\n> worktree, and the user cannot use the picker to create a new worktree\n> inside another.\n> \n> So, add two new explicit subcommands:\n>     gui  - Run the gui if repository/worktree discovery succeeds, or die\n>            with an error message, but never run the picker.\n>     pick - First run the picker, regardless, then start the gui in\n>            the chosen worktree.\n> \n> Nothing in this changes the prior behavior, the alternates above must be\n> explicitly selected to see any change.\n\nOK.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 22 ++++++++++++++++++++--\n>  1 file changed, 20 insertions(+), 2 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 3a83dd5..c56aeef 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1021,6 +1021,7 @@ proc load_config {include_global} {\n>  ##\n>  ## feature option selection\n>  \n> +set run_picker_on_error 1\n>  if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n>  \tunset _junk\n>  } else {\n> @@ -1030,6 +1031,7 @@ if {$subcommand eq {gui.sh}} {\n>  \tset subcommand gui\n>  }\n>  if {$subcommand eq {gui} && [llength $argv] > 0} {\n> +\tset run_picker_on_error 0\n>  \tset subcommand [lindex $argv 0]\n>  \tset argv [lrange $argv 1 end]\n>  }\n> @@ -1047,6 +1049,7 @@ blame {\n>  \tdisable_option multicommit\n>  \tdisable_option branch\n>  \tdisable_option transport\n> +\tset run_picker_on_error 0\n>  }\n>  citool {\n>  \tenable_option singlecommit\n> @@ -1055,6 +1058,7 @@ citool {\n>  \tdisable_option multicommit\n>  \tdisable_option branch\n>  \tdisable_option transport\n> +\tset run_picker_on_error 0\n>  \n>  \twhile {[llength $argv] > 0} {\n>  \t\tset a [lindex $argv 0]\n\nCan we please use the available disable_option and enable_option feature\ninstead of a new variable. Just for consistency around repository discovery.\n\n> @@ -1162,14 +1166,28 @@ proc pick_repo {} {\n>  \tset picked 1\n>  }\n>  \n> +# run repository picker if explicitly requested\n> +switch -- $subcommand {\n> +\tpick {\n> +\t\tpick_repo\n> +\t\tset subcommand gui\n> +\t\tset run_picker_on_error 0\n> +\t}\n> +}\n> +\n\nIt just feels wrong to have a new pick_repo call before repository\ndiscovery. Can we not treat this case below as if regular repository\ndiscovery failed and then end up in the existing call of pick_repo?\n\n>  # find repository.\n>  if {[catch {\n>  \tset _gitdir [git rev-parse --absolute-git-dir]\n>  } err]} {\n>  \tif {[is_gitvars_error $err]} {\n>  \t\texit 1\n> -\t} else {\n> +\t}\n> +\tif {$run_picker_on_error} {\n>  \t\tpick_repo\n> +\t} else {\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n> +\t\texit 1\n>  \t}\n>  }\n>  \n> @@ -3051,7 +3069,7 @@ gui {\n>  \t# fall through to setup UI for commits\n>  }\n>  default {\n> -\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool}\\]\"\n> +\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool|gui|pick}\\]\"\n>  \tif {[tk windowingsystem] eq \"win32\"} {\n>  \t\twm withdraw .\n>  \t\ttk_messageBox -icon error -message $err \\\n\nWe don't need to switch on the new subcommands?\n\nAs a follow-up to my comment on 04/11: How relevant is it that variable\n$picked is set in a 'git gui pick' invocation?\n\n-- Hannes\n\n"},{"id":"543444","messageId":"c5147296-06e1-4f7d-8f4f-f424e26e5b13@kdbg.org","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"Re: [PATCH v1 00/11] Improve git gui operation without a worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-16T08:28:58Z","receivedAt":"2026-05-16T08:29:04Z","isPatch":true,"body":"Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n> git gui has a number of inter-related problems that result in problems\n> during startup from anything but a checked out worktree pointing at a\n> valid git repository. Some of the symptoms are:\n> - blame / browser subcommands, and launching gitk, are intended to be\n>   useful without a worktree, but fail to work.\n> - unlike git, git-gui is supposed to use the parent directory as a\n>   worktree if started from the .git subdirectory in the very common\n>   single worktree + embedded git repository format. This does not\n>   work.\n> - git-gui includes a repository picker allowing a user select a worktree\n>   from a list and/or start a new repo+worktree: this dialog appears at\n>   unexpected times, masking useful error feedback on configuration\n>   problems.\n> \n> This patch series addresses the above issues, substantially rewriting\n> the blame / browser command line process, the initial repository and\n> worktree discovery processes, and using git rev-parse when possible to\n> handle repository / worktree discovery including any specification of\n> GIT_DIR or GIT_WORK_TREE to reduce the future likelihood of conflict\n> with command line git. This also allows explicit user control to avoid\n> the repository picker masking a configuration error.\n\nOK. Overall, this goes in the right direction. There are still open\nquestions and potential problems with this implementation. We also\ndisagree in a few details; see my comments on the patches.\n\n> \n> Note: I question why git-gui ever exports GIT_WORK_TREE. If it is not\n> empty, that is the current directory when startup is complete and any\n> git command will use the current directory as the worktree.\n\nI fully agree with this.\n\n> If empty,\n> there is no worktree and the current directory should be (and after this\n> series, is) at the toplevel of the gitdir: again, there is nothing to\n> communicate to another process.\n\nHere I disagree. We should not need to change directory if no working\ntree was found.\n\n> If a process being launched needs a\n> different worktree, that should be the startup directory given to the\n> process without changing git-gui's current directory.\n\nI haven't thought this through, but this sounds very reasonable.\n\n> \n> Mark Levedahl (11):\n>   git-gui: allow specifying path '.' to the browser\n>   git-gui: refactor browser / blame argument parsing\n>   git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE\n>   git-gui: put choose_repository::pick in a proc\n>   git-gui: use --absolute-git-dir\n>   git gui: GIT_DIR / GIT_WORK_TREE make any discovery error fatal\n>   git-gui: use rev-parse exclusively to find a repository\n>   git-gui: simplify [is_bare] to report if a worktree is known\n>   git-gui: support using repository parent dir as a worktree\n>   git-gui: improve worktree discovery\n>   git-gui: add gui and pick as explicit subcommands\n> \n>  git-gui.sh | 276 ++++++++++++++++++++++++++---------------------------\n>  1 file changed, 135 insertions(+), 141 deletions(-)\n> \n\n-- Hannes\n\n"},{"id":"543452","messageId":"9ea3c399-70d4-4f4d-80bf-159d1086c15e@gmail.com","threadId":"65531","inReplyTo":"796217c3-8998-47a8-9a46-298541708d41@kdbg.org","subject":"Re: [PATCH v1 01/11] git-gui: allow specifying path '.' to the browser","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T13:38:35Z","receivedAt":"2026-05-16T13:38:37Z","isPatch":true,"body":"\n\nOn 5/15/26 11:54 AM, Johannes Sixt wrote:\n> The description isn't precise, though. '.' means to list the current\n> directory. The mentioned problem happens only if this is also the root\n> of the working tree.\nEasy to fix, will do.\n\nMark\n"},{"id":"543455","messageId":"465bcbef-bec9-4f7c-b55f-ed60f23cbcb2@gmail.com","threadId":"65531","inReplyTo":"cb3012ab-1a97-4197-bc57-34eb3fa472a2@kdbg.org","subject":"Re: [PATCH v1 02/11] git-gui: refactor browser / blame argument parsing","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:21:28Z","receivedAt":"2026-05-16T14:21:30Z","isPatch":true,"body":"\n\nOn 5/15/26 11:56 AM, Johannes Sixt wrote:\nsnip...\n> The check for the existence of files is actually necessary to\n> disambiguate the meaning of the argument. If a file \"maint\" exists, then\n> the argument is to be interpreted as path, not as the ref \"maint\", even\n> if that exists, too.\n>\n> I suggest to protect the \"file exists\" calls with ($_gitworktree ne {}\n> && ...) or (![is_bare] && ...) to handle being invoked from a bare\n> repository. That is, in a bare repository we treat arguments the same as\n> files that do not exist in the currently checked-out branch.\n\nLet me start over, addressing only the use-case in a bare repository.\n\nMark\n\n"},{"id":"543456","messageId":"a70e90a5-bcce-4935-b804-92fcc1fc28dd@gmail.com","threadId":"65531","inReplyTo":"d9af8b60-e354-4cfc-87b3-a3e708b362da@kdbg.org","subject":"Re: [PATCH v1 03/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:25:26Z","receivedAt":"2026-05-16T14:25:28Z","isPatch":true,"body":"\n\nOn 5/15/26 11:58 AM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui unconditionally exports GIT_DIR and GIT_WORK_TREE to the\n>> environment, and furthmore unconditionally unsets these in many places.\n>> But, GIT_WORK_TREE should be set only if it is not {} as the empty\n>> value, really meaning no work-tree is found, causes git to throw fatal\n>> errors (git-gui gets the error from branch --show-current).  Fixing this\n>> is required to allow blame and browser to operate from a repository\n>> without a worktree.\n>>\n>> Establish a pair of functions to remove GIT_DIR and GIT_WORK_TREE from\n>> the environment, avoiding any error if they do not exist. Also, add a\n>> function to export these, but export GIT_WORK_TREE only if not empty.\n> Good. But as I said in a parallel thread, I actually concur with your\n> assessment in the coverletter of this patch series that GIT_WORK_TREE\n> should be not set at all. At least in the modes that require a working tree.\n>\n>> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n>> ---\n>>  git-gui.sh | 32 ++++++++++++++++++++++----------\n>>  1 file changed, 22 insertions(+), 10 deletions(-)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index a951fcd..387cad6 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -1122,6 +1122,22 @@ unset argv0dir\n>>  ##\n>>  ## repository setup\n>>  \n>> +proc set_gitdir_vars {} {\n>> +\tglobal _gitdir _gitworktree env\n>> +\tif {$_gitdir ne {}} {\n>> +\t\tset env(GIT_DIR) $_gitdir\n>> +\t}\n>> +\tif {$_gitworktree ne {}} {\n>> +\t\tset env(GIT_WORK_TREE) $_gitworktree\n>> +\t}\n>> +}\n>> +\n>> +proc unset_gitdir_vars {} {\n>> +\tglobal env\n>> +\tcatch {unset env(GIT_DIR)}\n>> +\tcatch {unset env(GIT_WORK_TREE)}\n>> +}\n>> +\n>>  set picked 0\n>>  if {[catch {\n>>  \t\tset _gitdir $env(GIT_DIR)\n>> @@ -1207,8 +1223,8 @@ if {[lindex $_reponame end] eq {.git}} {\n>>  \tset _reponame [lindex $_reponame end]\n>>  }\n>>  \n>> -set env(GIT_DIR) $_gitdir\n>> -set env(GIT_WORK_TREE) $_gitworktree\n>> +# Export the final paths\n>> +set_gitdir_vars\n>>  \n>>  ######################################################################\n>>  ##\n>> @@ -2050,13 +2066,11 @@ proc do_gitk {revs {is_submodule false}} {\n>>  \t\t\t# TODO we could make life easier (start up faster?) for gitk\n>>  \t\t\t# by setting these to the appropriate values to allow gitk\n>>  \t\t\t# to skip the heuristics to find their proper value\n>> -\t\t\tunset env(GIT_DIR)\n>> -\t\t\tunset env(GIT_WORK_TREE)\n>> +\t\t\tunset_gitdir_vars\n>>  \t\t}\n>>  \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n>>  \n>> -\t\tset env(GIT_DIR) $_gitdir\n>> -\t\tset env(GIT_WORK_TREE) $_gitworktree\n>> +\t\tset_gitdir_vars\n>>  \t\tcd $pwd\n>>  \n>>  \t\tif {[info exists main_status]} {\n>> @@ -2084,16 +2098,14 @@ proc do_git_gui {} {\n>>  \n>>  \t\t# see note in do_gitk about unsetting these vars when\n>>  \t\t# running tools in a submodule\n>> -\t\tunset env(GIT_DIR)\n>> -\t\tunset env(GIT_WORK_TREE)\n>> +\t\tunset_gitdir_vars\n>>  \n>>  \t\tset pwd [pwd]\n>>  \t\tcd $current_diff_path\n>>  \n>>  \t\tsafe_exec_bg [concat $exe gui]\n>>  \n>> -\t\tset env(GIT_DIR) $_gitdir\n>> -\t\tset env(GIT_WORK_TREE) $_gitworktree\n>> +\t\tset_gitdir_vars\n>>  \t\tcd $pwd\n>>  \n>>  \t\tset status_operation [$::main_status \\\n> After these changes, a 'global env' probably becomes stale and could be\n> removed.\n>\n> -- Hannes\n>\nWill update to only ever set GIT_DIR, will still remove GIT_WORK_TREE on unset.\n\nMark\n"},{"id":"543457","messageId":"d56431e8-79b5-428d-9bfa-e814e41af3c5@gmail.com","threadId":"65531","inReplyTo":"7544deeb-163c-4444-833a-7b840a7caa4a@kdbg.org","subject":"Re: [PATCH v1 04/11] git-gui: put choose_repository::pick in a proc","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:29:44Z","receivedAt":"2026-05-16T14:29:46Z","isPatch":true,"body":"\n\nOn 5/15/26 11:59 AM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui includes a 'repository picker', which allows creating a new\n>> repository + worktree, or selecting a worktree from a recent list.\n>> git-gui runs the picker when a valid git repository is not found. All of\n>> the code for this is embedded in the discovery process block, making the\n>> latter more difficult to read, and also making things more difficult if\n>> we want to have an explicit 'pick' subcommand to force this to run.\n> OK, let's see how useful this becomes.\n>\n>> Let's move this invocation and supporting code to a separate proc,\n>> aiding in subsequent refactoring. Assure GIT_DIR and GIT_WORK_TREE are\n>> unset, configuration is loaded, ant that _gitdir is correctly set\n> s/ant/and/\nwill fix\n>> afterwards. As this is invoked before worktree discovery, later code\n>> will set that anyway so need not be included here.\n>>\n>> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n>> ---\n>>  git-gui.sh | 18 +++++++++++-------\n>>  1 file changed, 11 insertions(+), 7 deletions(-)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index 387cad6..0b73c35 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -1139,6 +1139,16 @@ proc unset_gitdir_vars {} {\n>>  }\n>>  \n>>  set picked 0\n>> +proc pick_repo {} {\n>> +\tunset_gitdir_vars\n>> +\tload_config 1\n>> +\tapply_config\n>> +\tchoose_repository::pick\n>> +\tset _gitdir [git rev-parse --absolute-git-dir]\n>> +\tset _prefix {}\n>> +\tset picked 1\n>> +}\n>> +\n> So, this isn't intended as a plain move of code? Since we set _gitdir\n> here, we could remove the corresonding lines from lib/choose_repository.tcl.\nit should be a plain move.\n> Is the variable \"picked\" only needed for this particular picker\n> invocation? Then it should not be set in the function, but at the call site.\nI need to better understand how \"picked\" is used to decide... will do before an update.\n>>  if {[catch {\n>>  \t\tset _gitdir $env(GIT_DIR)\n>>  \t\tset _prefix {}\n>> @@ -1149,13 +1159,7 @@ if {[catch {\n>>  \t\tset _gitdir [git rev-parse --git-dir]\n>>  \t\tset _prefix [git rev-parse --show-prefix]\n>>  \t} err]} {\n>> -\tload_config 1\n>> -\tapply_config\n>> -\tchoose_repository::pick\n>> -\tif {![file isdirectory $_gitdir]} {\n>> -\t\texit 1\n>> -\t}\n>> -\tset picked 1\n>> +\t\tpick_repo\n> The indentation is off here.\nwill fix.\n\nMark\n"},{"id":"543458","messageId":"e5eb5105-6e6d-4e68-afc5-107e4b4a8472@gmail.com","threadId":"65531","inReplyTo":"948f9f9f-8225-4bfe-be7d-e9b03c912aeb@kdbg.org","subject":"Re: [PATCH v1 05/11] git-gui: use --absolute-git-dir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:33:27Z","receivedAt":"2026-05-16T14:33:29Z","isPatch":true,"body":"\n\nOn 5/15/26 12:00 PM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui uses git rev-parse --git-dir to get the pathname of the\n>> discovered git repository. The returned value can be relative, and is\n>> '.' if the current directory is the top of the repository directory\n>> itself.  git-gui has code to change '.' to [pwd] in this case so that\n>> subsequent logic runs.\n>>\n>> But, git rev-parse supports --absolute-git-dir from fac60b8925\n>> (\"rev-parse: add option for absolute or relative path formatting\",\n>> 2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\n>> this more useful form is always available. Use --absolute-git-dir to\n>> always get an absolute path, avoiding the need for other checks.\n> Nice!\n>\n> However, the patch is incomplete. We set _gitdir also from\n> lib/choose_repository.tcl. I think it would be best to swap this patch\n> with patch 4/11, remove the _gitdir setters from the picker\n> implementation, and call `rev-parse --absolute-git-dir` like you did in\n> 4/11. This depends on that the picker sets the current directory to the\n> top-level of the working tree with the embeded .git directory.\n>\n> BTW, missing sign-off.\nI will change the interface to the picker so that success / failure is a returned value\nrather than _gitdir being non-empty, then rework order and content of these patches.\n\nMark\n"},{"id":"543459","messageId":"62ed0001-5769-47f7-b31d-4948d498555c@gmail.com","threadId":"65531","inReplyTo":"d8844726-0b08-4035-946e-c5ada0759f32@kdbg.org","subject":"Re: [PATCH v1 07/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:38:48Z","receivedAt":"2026-05-16T14:38:50Z","isPatch":true,"body":"\n\nOn 5/15/26 12:06 PM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui attempts to use env(GIT_DIR) directly as the git repository,\n>> accepting GIT_DIR if it is a directory. Only if that fails is git\n>> rev-parse used to discover the repository.  But, this avoids all of\n>> git-core's validity checking on a repository, thus possibly deferring an\n>> error to a later step, possibly unexpected. Repository validation should\n>> be part of initial setup so that later processing does not need error\n>> trapping for configuration errors.\n> OK. If the user gave us GIT_DIR with our without GIT_WORK_TREE, then\n> that combination better be workable.\n>\n>> Let's just invoke rev-parse so all error checking is done. Stop here if\n>> the user set GIT_DIR or GIT_WORK_TREE. Otherwise, continue the existing\n>> behavior and show the repository picker.\n> OK. But the paragraph is confusing, because a big \"If an error occurs\"\n> is missing after the first sentence.\nwill fix.\n>> Also, remove a later check on whether _gitdir is a directory: that code\n>> cannot be reached without rev-parse having validating the repository.\n> Good.\n>\n>> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n>> ---\n>>  git-gui.sh | 24 +++++++++---------------\n>>  1 file changed, 9 insertions(+), 15 deletions(-)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index 2e2ddc0..81789dd 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -374,6 +374,7 @@ set _gitdir {}\n>>  set _gitworktree {}\n>>  set _isbare {}\n>>  set _githtmldir {}\n>> +set _prefix {}\n>>  set _reponame {}\n>>  set _shellpath {@@SHELL_PATH@@}\n>>  \n>> @@ -1167,19 +1168,18 @@ proc pick_repo {} {\n>>  \tset picked 1\n>>  }\n>>  \n>> +# find repository.\n>>  if {[catch {\n>> -\t\tset _gitdir $env(GIT_DIR)\n>> -\t\tset _prefix {}\n>> -\t\t}]\n>> -\t&& [catch {\n>> -\t\t# beware that from the .git dir this sets _gitdir to .\n>> -\t\t# and _prefix to the empty string\n>> -\t\tset _gitdir [git rev-parse --absolute-git-dir]\n>> -\t\tset _prefix [git rev-parse --show-prefix]\n>> -\t} err]} {\n>> +\tset _gitdir [git rev-parse --absolute-git-dir]\n> Please do also set _prefix. It should fix the bug that the file chooser\n> uses an empty prefix after\n>\n> cd lib\n> GIT_DIR=$PWD/../.git GIT_WORK_TREE=$PWD/.. ../git-gui.sh browser master .\n>\n> (this is an old bug.)\n>\n> Please keep the additional indentation of the catch body.\n>\n>> +} err]} {\n>> +\tif {[is_gitvars_error $err]} {\n>> +\t\texit 1\n>> +\t} else {\n>>  \t\tpick_repo\n>> +\t}\n> Treat the 'if' as an early exist without an else, and we don't need the\n> previously strange indentation of 'pick_repo'.\n>\n>>  }\n>>  \n>> +\n>>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>>  set hashalgorithm [git rev-parse --show-object-format]\n>>  if {$hashalgorithm eq \"sha1\"} {\n>> @@ -1191,12 +1191,6 @@ if {$hashalgorithm eq \"sha1\"} {\n>>  \texit 1\n>>  }\n>>  \n>> -if {![file isdirectory $_gitdir]} {\n>> -\tcatch {wm withdraw .}\n>> -\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n>> -\texit 1\n>> -}\n>> -\n>>  # _gitdir exists, so try loading the config\n>>  load_config 0\n>>  apply_config\n\nwill fix all.\n\nMark\n"},{"id":"543460","messageId":"6c189058-18f0-47cf-a663-d4d177282961@gmail.com","threadId":"65531","inReplyTo":"4d25544d-1a7e-4407-9191-1fb05ff55244@kdbg.org","subject":"Re: [PATCH v1 09/11] git-gui: support using repository parent dir as a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T14:48:26Z","receivedAt":"2026-05-16T14:48:28Z","isPatch":true,"body":"\n\nOn 5/16/26 4:14 AM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n>> 2010-01-23), has had the intent to allow starting from inside a\n>> repository, then switching to the parent directory if that is a valid\n>> worktree.\n>>\n>> This certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n>> --show-toplevel without a worktree an error\", 2019-11-19) in git, but\n>> breaking this git-gui feature was unintentional.\n>>\n>> Add a proc to test if the parent of the git repository is a valid\n>> worktree, and set that directory as the worktree if so. Use invocations\n>> of git rev-parse to assure all validity and safety checks included in\n>> git-core are executed.\n> BTW, missing sign-off.\n>\n>> ---\n>>  git-gui.sh | 17 +++++++++++++++++\n>>  1 file changed, 17 insertions(+)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index a03eaa7..e326401 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -1100,6 +1100,23 @@ unset argv0dir\n>>  ##\n>>  ## repository setup\n>>  \n>> +proc is_parent_worktree {} {\n>> +\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree\n>> +\tset ok 0\n>> +\tif {[file tail $::_gitdir] eq {.git}} {\n>> +\t\tset gitdir_parent [file join $::_gitdir {..}]\n>> +\t\tset expected_worktree [file normalize $gitdir_parent]\n> We have [file dirname ...]. Is there a reason to avoid it?\n>\n>> +\t\tcatch {set git_worktree [git -C $gitdir_parent rev-parse --show-toplevel]}\n>> +\t\tif {[string compare $expected_worktree $git_worktree] == 0} {\n> The purpose of this check should be explained in a comment. I think it is:\n>\n> For a repository with the database in a directory named .git we assume\n> that the working tree is the directory containing .git. But\n> configuration may point to a different worktree. Then we do not want to\n> hold on to our assumption.\n>\n> However, whether [git -C elsewhere ...] uses the same gitdir that we\n> have discovered so far cannot be told from this piece of code alone.\n> Therefore, I think it is wrong to extract this check into a function.\n>\n> Also, I don't think we can use string comparison here. On Windows, the\n> command returns the Windows style path, but Tcl my operate with a POSIX\n> style path.\nAs you have correctly inferred, am trying to unambiguously establish that git running in\nthe parent directory is using  the child .git as the repository. I think this actually\nrequires two calls to git-revparse (--absolute-git-dir and --show-toplevel).\n- the current git repo is valid to support a worktree. Will rework.\n\n>> +\t\t\tset ::_prefix {}\n>> +\t\t\tset ::_gitworktree $git_worktree\n>> +\t\t\tcd $git_worktree\n> So many side-effects in a function whose name suggests that it only does\n> some checks. Please, don't do that.\n>\n>> +\t\t\tset ok 1\n>> +\t\t}\n>> +\t}\n>> +\treturn $ok\n>> +}\n>> +\n>>  proc is_gitvars_error {err} {\n>>  \tset havevars 0\n>>  \tset GIT_DIR {}\n> In general, I am not a fan of commits that add new functions, but no\n> call sites. Please squash this into 10/11. Ditto for is_gitvars_error in\n> 06/11.\n>\n> -- Hannes\n>\n\nNext round should address all of your comments.\nMark\n"},{"id":"543466","messageId":"f654eab3-2157-4591-9ae0-440efb052e8e@gmail.com","threadId":"65531","inReplyTo":"8b8feffa-1651-41aa-ac76-d2721d656b45@kdbg.org","subject":"Re: [PATCH v1 10/11] git-gui: improve worktree discovery","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T15:28:57Z","receivedAt":"2026-05-16T15:28:59Z","isPatch":true,"body":"\n\nOn 5/16/26 4:16 AM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git gui's worktree discovery needs update based upon prior work in this\n>> series. In the normal case, all information we need comes directly from\n>> git rev-parse (--show-toplevel, and --show-prefix). Should this work, we\n>> have a valid worktree and all git gui commands can run.\n>>\n>> If not, we need to consider:\n>> - if GIT_DIR or GIT_WORK_TREE are in the environment, just stop as we\n>>   the input configuration was wrong, the user must fix that.\n>> - if we have a browser or blame subcommand, no worktree is needed so\n>>   git-gui can run without.\n>> - using the git repository's parent is a valid worktree (if possible),\n>>   restoring prior behavior.\n>>\n>> The current directory should be either the root of the worktree, if one\n>> is found, or the top-level of the git repository.\n> I disagree in the case where no working tree is found. Then there is no\n> point in changing the current directory.\n\ngit-gui always changes to root of the worktree if that is found.\nFailing to cd to the root of the repository when operating with no worktree opens the\npossibility th\n>\n>> Make it so. Also, make worktree discover directly follow repository\n>> discovery, reducing the locations that might need error trapping to\n>> catch configuration issues.\n> Good!\n>\n>> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> BTW, please make the subject line more descriptive. The word \"improve\"\n> does not convey anything of importance.\n>\n>> ---\n>>  git-gui.sh | 56 ++++++++++++++++++++++--------------------------------\n>>  1 file changed, 23 insertions(+), 33 deletions(-)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index e326401..3a83dd5 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -1173,6 +1173,28 @@ if {[catch {\n>>  \t}\n>>  }\n>>  \n>> +# find worktree, continue without if not required\n>> +if {[catch {\n>> +\tset _gitworktree [git rev-parse --show-toplevel]\n>> +\tset _prefix [git rev-parse --show-prefix]\n>> +\tcd $_gitworktree\n>> +} err]} {\n> We have three commands, each with their own possible failure sources.\n> One of the outcomes of an error is that we proceed anyway. I think that\n> this is incorrect if the 'cd' fails: we must not proceed if it fails.\n> Therefore, we must handle its failure separately.\n>\n>> +\tif {[is_gitvars_error $err]} {\n>> +\t\texit 1\n>> +\t}\n>> +\tset _gitworktree {}\n>> +\tset _prefix {}\n>> +\tif {[is_enabled bare]} {\n>> +\t\tcd $_gitdir\n> Why change the directory here? If we run `git gui browser master dir` we\n> do not want to change the directory in an uncontrolled manner. The\n> argument parser will want to check for the existence of files, and then\n> we do not want to operate from a random directory.\n>\n> Also, I think that the check must be for [is_bare] and not [is_enabled\n> bare].\n\n[is_enabled_bare] is correct. This code handles the case: \n    - neither the startup directory nor GIT_WORK_TREE are useable worktrees, so [is_bare]\nis currently true.\n    - the command given is browser or blame so a worktree is not needed. We can proceed.\n\nThe bigger question is whether to change directory at all: git-gui should never touch\nfiles that are neither in the worktree nor in the repository. Leaving the current\ndirectory as neither of those could be troublesome. I have no strong feeling here though,\nwill delete this.\n    \n\n>> +\t} elseif {![is_parent_worktree]} {\n>> +\t\tcatch {wm withdraw .}\n>> +\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n>> +\t\texit 1\n>> +\t}\n>> +}\n>> +\n>> +# repository and worktree config are complete, export them\n>> +set_gitdir_vars\n>>  \n>>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>>  set hashalgorithm [git rev-parse --show-object-format]\n> This moves code around. In particular, we see load_config and\n> apply_config in the context below, which now happens only after these\n> calls. How certain are we that these have no effect on the code that\n> runs now earlier?\nWe need to load the system and user global config before running the repository picker. We\n(re-) load the full config including the repository after we have a repository. I think\nthis is correct: git-config explicitly lists worktree dependent includeif statements,\nmeaning the config can be worktree dependent, and we must not load the final config until\nrepository and worktree discovery are complete.\n\ngit rev-parse, etc., perform discovery and config file loading each time they are invoked,\nthose are unaffected by git-gui's internal config.\n\nI will clarify this explicitly in the commit message.\n\n>> @@ -1189,37 +1211,8 @@ if {$hashalgorithm eq \"sha1\"} {\n>>  load_config 0\n>>  apply_config\n>>  \n>> -set _gitworktree [git rev-parse --show-toplevel]\n>>  \n>> -if {$_prefix ne {}} {\n>> -\tif {$_gitworktree eq {}} {\n>> -\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n>> -\t} else {\n>> -\t\tset cdup $_gitworktree\n>> -\t}\n>> -\tif {[catch {cd $cdup} err]} {\n>> -\t\tcatch {wm withdraw .}\n>> -\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n>> -\t\texit 1\n>> -\t}\n>> -\tset _gitworktree [pwd]\n>> -\tunset cdup\n>> -} elseif {![is_enabled bare]} {\n>> -\tif {[is_bare]} {\n>> -\t\tcatch {wm withdraw .}\n>> -\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n>> -\t\texit 1\n>> -\t}\n>> -\tif {$_gitworktree eq {}} {\n>> -\t\tset _gitworktree [file dirname $_gitdir]\n>> -\t}\n>> -\tif {[catch {cd $_gitworktree} err]} {\n>> -\t\tcatch {wm withdraw .}\n>> -\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n>> -\t\texit 1\n>> -\t}\n>> -\tset _gitworktree [pwd]\n>> -}\n>> +# Derive a human-readable repository name\n>>  set _reponame [file split [file normalize $_gitdir]]\n>>  if {[lindex $_reponame end] eq {.git}} {\n>>  \tset _reponame [lindex $_reponame end-1]\n>> @@ -1227,9 +1220,6 @@ if {[lindex $_reponame end] eq {.git}} {\n>>  \tset _reponame [lindex $_reponame end]\n>>  }\n>>  \n>> -# Export the final paths\n>> -set_gitdir_vars\n>> -\n>>  ######################################################################\n>>  ##\n>>  ## global init\n> -- Hannes\n>\n\n"},{"id":"543467","messageId":"fad43240-1089-4447-b97d-ee553c34eef1@gmail.com","threadId":"65531","inReplyTo":"e336544b-941d-43ed-890f-2b8950dbaf88@kdbg.org","subject":"Re: [PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-16T15:42:17Z","receivedAt":"2026-05-16T15:42:19Z","isPatch":true,"body":"\n\nOn 5/16/26 4:18 AM, Johannes Sixt wrote:\n> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>> git-gui accepts subcommands blame | browser | citool, and assumes the\n>> subcommand is 'gui' if none is actually given, But, git gui also has a\n>> repository picker (choose_repository::pick) that can create a new\n>> repository + worktree, or choose an existing one, switch to that, and\n>> the run the gui. The user has no direct control over invoking the\n>> picker, instead the picker is triggered by failure in the repository /\n>> worktree discover process: this includes being started in a directory\n>> not controlled by git, which is probably the intended use case.\n>>\n>> The picker can appear when the user has no intention of creating a new\n>> worktree, and the user cannot use the picker to create a new worktree\n>> inside another.\n>>\n>> So, add two new explicit subcommands:\n>>     gui  - Run the gui if repository/worktree discovery succeeds, or die\n>>            with an error message, but never run the picker.\n>>     pick - First run the picker, regardless, then start the gui in\n>>            the chosen worktree.\n>>\n>> Nothing in this changes the prior behavior, the alternates above must be\n>> explicitly selected to see any change.\n> OK.\n>\n>> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n>> ---\n>>  git-gui.sh | 22 ++++++++++++++++++++--\n>>  1 file changed, 20 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/git-gui.sh b/git-gui.sh\n>> index 3a83dd5..c56aeef 100755\n>> --- a/git-gui.sh\n>> +++ b/git-gui.sh\n>> @@ -1021,6 +1021,7 @@ proc load_config {include_global} {\n>>  ##\n>>  ## feature option selection\n>>  \n>> +set run_picker_on_error 1\n>>  if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n>>  \tunset _junk\n>>  } else {\n>> @@ -1030,6 +1031,7 @@ if {$subcommand eq {gui.sh}} {\n>>  \tset subcommand gui\n>>  }\n>>  if {$subcommand eq {gui} && [llength $argv] > 0} {\n>> +\tset run_picker_on_error 0\n>>  \tset subcommand [lindex $argv 0]\n>>  \tset argv [lrange $argv 1 end]\n>>  }\n>> @@ -1047,6 +1049,7 @@ blame {\n>>  \tdisable_option multicommit\n>>  \tdisable_option branch\n>>  \tdisable_option transport\n>> +\tset run_picker_on_error 0\n>>  }\n>>  citool {\n>>  \tenable_option singlecommit\n>> @@ -1055,6 +1058,7 @@ citool {\n>>  \tdisable_option multicommit\n>>  \tdisable_option branch\n>>  \tdisable_option transport\n>> +\tset run_picker_on_error 0\n>>  \n>>  \twhile {[llength $argv] > 0} {\n>>  \t\tset a [lindex $argv 0]\n> Can we please use the available disable_option and enable_option feature\n> instead of a new variable. Just for consistency around repository discovery.\n>\n>> @@ -1162,14 +1166,28 @@ proc pick_repo {} {\n>>  \tset picked 1\n>>  }\n>>  \n>> +# run repository picker if explicitly requested\n>> +switch -- $subcommand {\n>> +\tpick {\n>> +\t\tpick_repo\n>> +\t\tset subcommand gui\n>> +\t\tset run_picker_on_error 0\n>> +\t}\n>> +}\n>> +\n> It just feels wrong to have a new pick_repo call before repository\n> discovery. Can we not treat this case below as if regular repository\n> discovery failed and then end up in the existing call of pick_repo?\n\nSo, your suggestion is to create an error inside the catch clause, assure GIT_VAR and\nGIT_WORK_TREE are unset so we don't throw and error message and abort, and then fall\nthrough to the existing pick_repo clause?\n\nI think this makes code less understandable and more complicated. As written, the user\nselects the repo, and now the standard discovery runs except that the picker cannot be re-run.\n\nI like the current structure better.\n\n>>  # find repository.\n>>  if {[catch {\n>>  \tset _gitdir [git rev-parse --absolute-git-dir]\n>>  } err]} {\n>>  \tif {[is_gitvars_error $err]} {\n>>  \t\texit 1\n>> -\t} else {\n>> +\t}\n>> +\tif {$run_picker_on_error} {\n>>  \t\tpick_repo\n>> +\t} else {\n>> +\t\tcatch {wm withdraw .}\n>> +\t\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n>> +\t\texit 1\n>>  \t}\n>>  }\n>>  \n>> @@ -3051,7 +3069,7 @@ gui {\n>>  \t# fall through to setup UI for commits\n>>  }\n>>  default {\n>> -\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool}\\]\"\n>> +\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool|gui|pick}\\]\"\n>>  \tif {[tk windowingsystem] eq \"win32\"} {\n>>  \t\twm withdraw .\n>>  \t\ttk_messageBox -icon error -message $err \\\n> We don't need to switch on the new subcommands?\n>\n> As a follow-up to my comment on 04/11: How relevant is it that variable\n> $picked is set in a 'git gui pick' invocation?\n>\n> -- Hannes\n>\nRepeating, I need to understand what \"picked\" really does in the gui. Will definitely\naddress your question.\n\nMark\n\n"},{"id":"543600","messageId":"5081fcc5-19b5-49aa-a33c-2c13aba7edb1@kdbg.org","threadId":"65531","inReplyTo":"f654eab3-2157-4591-9ae0-440efb052e8e@gmail.com","subject":"Re: [PATCH v1 10/11] git-gui: improve worktree discovery","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-19T08:16:11Z","receivedAt":"2026-05-19T08:16:20Z","isPatch":true,"body":"Am 16.05.26 um 17:28 schrieb Mark Levedahl:\n> On 5/16/26 4:16 AM, Johannes Sixt wrote:\n>> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>>> +\tif {[is_gitvars_error $err]} {\n>>> +\t\texit 1\n>>> +\t}\n>>> +\tset _gitworktree {}\n>>> +\tset _prefix {}\n>>> +\tif {[is_enabled bare]} {\n>>> +\t\tcd $_gitdir\n>> Why change the directory here? If we run `git gui browser master dir` we\n>> do not want to change the directory in an uncontrolled manner. The\n>> argument parser will want to check for the existence of files, and then\n>> we do not want to operate from a random directory.\n>>\n>> Also, I think that the check must be for [is_bare] and not [is_enabled\n>> bare].\n> \n> [is_enabled_bare] is correct. This code handles the case: \n>     - neither the startup directory nor GIT_WORK_TREE are useable worktrees, so [is_bare]\n> is currently true.\n>     - the command given is browser or blame so a worktree is not needed. We can proceed.\n\nBut in the case where the command is browser or blame, the argument\nparser must later check for the existence of files, provided that a\nworktree is present. But this conditional would change directory to\nsomewhere that is not a worktree at all even though a worktree is\navailable. So, I am still convinced that [is_bare] is correct.\n\n> The bigger question is whether to change directory at all: git-gui should never touch\n> files that are neither in the worktree nor in the repository. Leaving the current\n> directory as neither of those could be troublesome. I have no strong feeling here though,\n> will delete this.\n\nOK. We need the conditional, but not change the directory.\n\n>>> +\t} elseif {![is_parent_worktree]} {\n>>> +\t\tcatch {wm withdraw .}\n>>> +\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n>>> +\t\texit 1\n>>> +\t}\n>>> +}\n>>> +\n>>> +# repository and worktree config are complete, export them\n>>> +set_gitdir_vars\n>>>  \n>>>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>>>  set hashalgorithm [git rev-parse --show-object-format]\n>> This moves code around. In particular, we see load_config and\n>> apply_config in the context below, which now happens only after these\n>> calls. How certain are we that these have no effect on the code that\n>> runs now earlier?\n> We need to load the system and user global config before running the repository picker. We\n> (re-) load the full config including the repository after we have a repository. I think\n> this is correct: git-config explicitly lists worktree dependent includeif statements,\n> meaning the config can be worktree dependent, and we must not load the final config until\n> repository and worktree discovery are complete.\n\nGood point.\n\n> \n> git rev-parse, etc., perform discovery and config file loading each time they are invoked,\n> those are unaffected by git-gui's internal config.\n\nAlso very true. Only calls to proc get_config would be affected by a\ndifferent setup, but there are none in the code that has swapped places\nwith working tree discovery. Only --show-object-format would be affected\nby the discovered environment, and for that case it is more correct to\noperate in the final setup.\n\n-- Hannes\n\n"},{"id":"543602","messageId":"3b16fbc6-074b-410d-861e-6f77794b02a0@kdbg.org","threadId":"65531","inReplyTo":"fad43240-1089-4447-b97d-ee553c34eef1@gmail.com","subject":"Re: [PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-19T08:21:55Z","receivedAt":"2026-05-19T08:22:04Z","isPatch":true,"body":"Am 16.05.26 um 17:42 schrieb Mark Levedahl:\n> On 5/16/26 4:18 AM, Johannes Sixt wrote:\n>> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>>> diff --git a/git-gui.sh b/git-gui.sh\n>>> index 3a83dd5..c56aeef 100755\n>>> --- a/git-gui.sh\n>>> +++ b/git-gui.sh\n>>> @@ -1021,6 +1021,7 @@ proc load_config {include_global} {\n>>>  ##\n>>>  ## feature option selection\n>>>  \n>>> +set run_picker_on_error 1\n>>>  if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n>>>  \tunset _junk\n>>>  } else {\n>>> @@ -1030,6 +1031,7 @@ if {$subcommand eq {gui.sh}} {\n>>>  \tset subcommand gui\n>>>  }\n>>>  if {$subcommand eq {gui} && [llength $argv] > 0} {\n>>> +\tset run_picker_on_error 0\n>>>  \tset subcommand [lindex $argv 0]\n>>>  \tset argv [lrange $argv 1 end]\n>>>  }\n>>> @@ -1047,6 +1049,7 @@ blame {\n>>>  \tdisable_option multicommit\n>>>  \tdisable_option branch\n>>>  \tdisable_option transport\n>>> +\tset run_picker_on_error 0\n>>>  }\n>>>  citool {\n>>>  \tenable_option singlecommit\n>>> @@ -1055,6 +1058,7 @@ citool {\n>>>  \tdisable_option multicommit\n>>>  \tdisable_option branch\n>>>  \tdisable_option transport\n>>> +\tset run_picker_on_error 0\n>>>  \n>>>  \twhile {[llength $argv] > 0} {\n>>>  \t\tset a [lindex $argv 0]\n>> Can we please use the available disable_option and enable_option feature\n>> instead of a new variable. Just for consistency around repository discovery.\n>>\n>>> @@ -1162,14 +1166,28 @@ proc pick_repo {} {\n>>>  \tset picked 1\n>>>  }\n>>>  \n>>> +# run repository picker if explicitly requested\n>>> +switch -- $subcommand {\n>>> +\tpick {\n>>> +\t\tpick_repo\n>>> +\t\tset subcommand gui\n>>> +\t\tset run_picker_on_error 0\n>>> +\t}\n>>> +}\n>>> +\n>> It just feels wrong to have a new pick_repo call before repository\n>> discovery. Can we not treat this case below as if regular repository\n>> discovery failed and then end up in the existing call of pick_repo?\n> \n> So, your suggestion is to create an error inside the catch clause, assure GIT_VAR and\n> GIT_WORK_TREE are unset so we don't throw and error message and abort, and then fall\n> through to the existing pick_repo clause?\n\n\nI think I would be happier with the structure\n\nif not subcommand pick\n    discover gitdir\n    if error\n        set subcommand pick\n\nif subcommand pick\n    pick_repo\n    set subcommand gui\n\nbecause this clarifies that pick_repo must erase all current traces of\nGIT_DIR and GIT_WORK_TREE from the envionment and must complete with a\nvalid setup.\n\nWith the structure in the proposed patch\n\nif subcommand pick\n    pick_repo\n    set subcommand gui\n\ndiscover gitdir\nif error\n    pick_repo\n\nwe still need the same operation of pick_repo, but after it runs due to\na pick command, we go into \"discover gitdir\" mode in an already modified\nenvironment, something that does not happen if pick_repo runs due to the\nerror in the gitdir discovery.\n\n-- Hannes\n\n"},{"id":"543701","messageId":"ffbbd733-2af9-4ff5-9354-cb2f333927a8@gmail.com","threadId":"65531","inReplyTo":"3b16fbc6-074b-410d-861e-6f77794b02a0@kdbg.org","subject":"Re: [PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-19T18:45:45Z","receivedAt":"2026-05-19T18:45:47Z","isPatch":true,"body":"\n\nOn 5/19/26 4:21 AM, Johannes Sixt wrote:\n> Am 16.05.26 um 17:42 \n>\n>\n> I think I would be happier with the structure\n>\n> if not subcommand pick\n>     discover gitdir\n>     if error\n>         set subcommand pick\n>\n> if subcommand pick\n>     pick_repo\n>     set subcommand gui\n>\n> because this clarifies that pick_repo must erase all current traces of\n> GIT_DIR and GIT_WORK_TREE from the envionment and must complete with a\n> valid setup.\n>\n> With the structure in the proposed patch\n>\n> if subcommand pick\n>     pick_repo\n>     set subcommand gui\n>\n> discover gitdir\n> if error\n>     pick_repo\n>\n> we still need the same operation of pick_repo, but after it runs due to\n> a pick command, we go into \"discover gitdir\" mode in an already modified\n> environment, something that does not happen if pick_repo runs due to the\n> error in the gitdir discovery.\n>\n> -- Hannes\n>\n\nWhat I have now is\n\nif (enabled gitdir discovery) {\n    discover gitdir\n    maybe an error occurs and gitdir remains {}\n}\n\nif (enabled pick && gitdir eq {}) {\n    unset GIT_DIR .. (just to be friendly, could throw an error instead...)\n    pick\n    discover gitdir to VALIDATE pick gave us a good thing\n}\n\nif no gitdir {\n    error No Repository\n}\n\nthen on to worktree discovery (which also validates what pick returns as pick may not have\ndone so).\n\nSo, we can independently enable normal discovery or pick, and with both enabled pick can\nbe used to recover from an error in normal discovery. Either way, there is only one block\nof code running pick, it is not a separate proc invoked multiple places. I hope this\nscratches your itch.\n\nStill scrubbing things, should send out in a day or two.\n\nMark\n"},{"id":"543702","messageId":"7851b418-0641-4365-aa03-4ee4d95509ea@gmail.com","threadId":"65531","inReplyTo":"5081fcc5-19b5-49aa-a33c-2c13aba7edb1@kdbg.org","subject":"Re: [PATCH v1 10/11] git-gui: improve worktree discovery","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-19T19:00:21Z","receivedAt":"2026-05-19T19:00:24Z","isPatch":true,"body":"\n\nOn 5/19/26 4:16 AM, Johannes Sixt wrote:\n> Am 16.05.26 um 17:28 schrieb Mark Levedahl:\n>> On 5/16/26 4:16 AM, Johannes Sixt wrote:\n>>> Am 14.05.26 um 16:33 schrieb Mark Levedahl:\n>>>> +\tif {[is_gitvars_error $err]} {\n>>>> +\t\texit 1\n>>>> +\t}\n>>>> +\tset _gitworktree {}\n>>>> +\tset _prefix {}\n>>>> +\tif {[is_enabled bare]} {\n>>>> +\t\tcd $_gitdir\n>>> Why change the directory here? If we run `git gui browser master dir` we\n>>> do not want to change the directory in an uncontrolled manner. The\n>>> argument parser will want to check for the existence of files, and then\n>>> we do not want to operate from a random directory.\n>>>\n>>> Also, I think that the check must be for [is_bare] and not [is_enabled\n>>> bare].\n>> [is_enabled_bare] is correct. This code handles the case: \n>>     - neither the startup directory nor GIT_WORK_TREE are useable worktrees, so [is_bare]\n>> is currently true.\n>>     - the command given is browser or blame so a worktree is not needed. We can proceed.\n> But in the case where the command is browser or blame, the argument\n> parser must later check for the existence of files, provided that a\n> worktree is present. But this conditional would change directory to\n> somewhere that is not a worktree at all even though a worktree is\n> available. So, I am still convinced that [is_bare] is correct.\nI did change this.   but...\nI have reworked the blame/browser parser so it fully matches git blame parsing for the\nsingle rev + path (or path rev) cases, all now do the same thing with or without a\nworktree as they all work from git history, so having a worktree becomes almost moot (I\nfound issues with how git-gui handles --, it is dead wrong in some cases if the intent is\nto match git-blame). What doesn't change is what blame displays based upon uncommitted or\nstaged changes in the worktree (if it does anything, but comments from 2007 suggest it\ndoes, I haven't tested). I've done nothing to change that, only finding the args to pass\nin to browser / blame. So, if a worktree exists, blame will still use the info there as it\ndoes now.\n\nMark\n"},{"id":"543707","messageId":"246039f4-8f61-4f7c-bd8a-50b7f519e7ae@kdbg.org","threadId":"65531","inReplyTo":"ffbbd733-2af9-4ff5-9354-cb2f333927a8@gmail.com","subject":"Re: [PATCH v1 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-19T21:15:57Z","receivedAt":"2026-05-19T21:16:08Z","isPatch":true,"body":"Am 19.05.26 um 20:45 schrieb Mark Levedahl:\n> What I have now is\n> \n> if (enabled gitdir discovery) {\n>     discover gitdir\n>     maybe an error occurs and gitdir remains {}\n> }\n> \n> if (enabled pick && gitdir eq {}) {\n>     unset GIT_DIR .. (just to be friendly, could throw an error instead...)\n>     pick\n>     discover gitdir to VALIDATE pick gave us a good thing\n> }\n> \n> if no gitdir {\n>     error No Repository\n> }\n> \n> then on to worktree discovery (which also validates what pick returns as pick may not have\n> done so).\n\nSounds good.\n\n-- Hannes\n\n"},{"id":"543749","messageId":"20260520202411.108764-1-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260514143322.865587-1-mlevedahl@gmail.com","subject":"[PATCH v2 00/11] Improve git gui operation without a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:23:59Z","receivedAt":"2026-05-20T20:24:29Z","isPatch":true,"body":"git gui has a number of inter-related problems that result in problems\nduring startup from anything but a checked out worktree pointing at a\nvalid git repository. Some of the symptoms are:\n- blame / browser subcommands, and launching gitk, are intended to be\n  useful without a worktree, but fail to work.\n- unlike git, git-gui is supposed to use the parent directory as a\n  worktree if started from the .git subdirectory in the very common\n  single worktree + embedded git repository format. This does not\n  work.\n- git-gui includes a repository picker allowing a user to select a\n  worktree from a list and/or start a new repo+worktree: this dialog can\n  appear at unexpected times, masking useful error feedback on\n  configuration problems.\n\nThis patch series addresses the above issues, substantially rewriting\nthe initial repository/worktree process to rely upon git rev-parse so\nthat git's knowledge of access rules, repository configuration, and use\nof GIT_DIR / GIT_WORK_TREE (or git --gitdir / --work-tree) is used\nthroughout, replacing code largely based upon what git did in 2008. This\nalso means that git gui will naturally gain any new rules implmented in\ngit-core.\n\nWith this, git-gui only exports GIT_WORK_TREE when non-empty.\nGIT_WORK_TREE is needed, and must be exported, if the user is overriding\ncore.worktree in the git repository. But, GIT_WORK_TREE cannot be used\nto specify the lack of a worktree, so exporting an empty GIT_WORK_TREE\nis one of the problems fixed by this series.\n\nv2 of this series is a very substantial rewrite driven by j6t's review,\nwith patches reoranized and squashed, interfaces to the repository\nchooser changed, a different code structure to allow user control of the\nrepository picker, a different approach to fixing the command line\nparser for blame / browser, and other more minor changes. Patches\nfor fixing blame / browser are now after all discovery refactoring as\nthey cannot be tested without some of those fixes.\n\nMany subtle things are fixed beyond the list at the top, including\nbetter compatibility with git blame and repeatable browser / blame\noperation for specific revs not in the worktree, regardless of the\nworktree state. j6t indicated that in the git-gui project, the following\nfails in the current release:\n\ncd lib\nGIT_DIR=$PWD/../.git GIT_WORK_TREE=$PWD/.. ../git-gui.sh browser origin/master .\n\nThis is due to a _prefix issue, and is fixed as of the patch\n     git-gui: use git rev-parse for worktree discovery\n\nMark Levedahl (11):\n  git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE\n  git-gui: return status from choose_repository::pick\n  git-gui: use --absolute-git-dir\n  git-gui: use rev-parse exclusively to find a repository\n  git-gui: simplify [is_bare] to report if a worktree is known\n  git-gui: use git rev-parse for worktree discovery\n  git-gui: try harder to find worktree from gitdir\n  git-gui: use HEAD as current branch when detached (bug fix)\n  git-gui: allow specifying path '.' to the browser\n  git-gui: adapt blame/browser parsing for bare operation\n  git-gui: add gui and pick as explicit subcommands\n\n git-gui.sh                | 412 +++++++++++++++++++++++---------------\n lib/choose_repository.tcl |  21 +-\n 2 files changed, 257 insertions(+), 176 deletions(-)\n\nInterdiff against v1:\ndiff --git a/git-gui.sh b/git-gui.sh\nindex c56aeeff88..299c1a0292 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -648,6 +648,9 @@ proc load_current_branch {} {\n \n \tset current_branch [git branch --show-current]\n \tset is_detached [expr [string length $current_branch] == 0]\n+\tif {$is_detached} {\n+\t\tset current_branch {HEAD}\n+\t}\n }\n \n auto_load tk_optionMenu\n@@ -1021,7 +1024,8 @@ proc load_config {include_global} {\n ##\n ## feature option selection\n \n-set run_picker_on_error 1\n+enable_option picker\n+enable_option gitdir_discovery\n if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n \tunset _junk\n } else {\n@@ -1031,9 +1035,11 @@ if {$subcommand eq {gui.sh}} {\n \tset subcommand gui\n }\n if {$subcommand eq {gui} && [llength $argv] > 0} {\n-\tset run_picker_on_error 0\n \tset subcommand [lindex $argv 0]\n \tset argv [lrange $argv 1 end]\n+\tif {$subcommand eq {gui}} {\n+\t\tdisable_option picker\n+\t}\n }\n \n enable_option multicommit\n@@ -1049,7 +1055,7 @@ blame {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n-\tset run_picker_on_error 0\n+\tdisable_option picker\n }\n citool {\n \tenable_option singlecommit\n@@ -1058,7 +1064,7 @@ citool {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n-\tset run_picker_on_error 0\n+\tdisable_option picker\n \n \twhile {[llength $argv] > 0} {\n \t\tset a [lindex $argv 0]\n@@ -1081,6 +1087,9 @@ citool {\n \t\tset argv [lrange $argv 1 end]\n \t}\n }\n+pick {\n+\tdisable_option gitdir_discovery\n+}\n }\n \n ######################################################################\n@@ -1104,21 +1113,39 @@ unset argv0dir\n ##\n ## repository setup\n \n-proc is_parent_worktree {} {\n-\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree\n-\tset ok 0\n+proc find_worktree_from_gitdir {} {\n+\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree.\n+\t# Assure parent is a worktree and using the git repository already discovered.\n+\t# Also, handle case of being in a worktree's gitdir, where file \"gitdir\" points to\n+\t# gitlink file .git in the real worktree.\n+\tset worktree {}\n \tif {[file tail $::_gitdir] eq {.git}} {\n-\t\tset gitdir_parent [file join $::_gitdir {..}]\n-\t\tset expected_worktree [file normalize $gitdir_parent]\n-\t\tcatch {set git_worktree [git -C $gitdir_parent rev-parse --show-toplevel]}\n-\t\tif {[string compare $expected_worktree $git_worktree] == 0} {\n-\t\t\tset ::_prefix {}\n-\t\t\tset ::_gitworktree $git_worktree\n-\t\t\tcd $git_worktree\n-\t\t\tset ok 1\n+\t\tif {[catch {\n+\t\t\tset gitdir_parent [file dirname $::_gitdir]\n+\t\t\tset worktree [git -C $gitdir_parent rev-parse --show-toplevel]\n+\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\tif {$::_gitdir ne $parent_gitdir} {\n+\t\t\t\tset worktree {}\n+\t\t\t}\n+\t\t}]} {\n+\t\t\tset worktree {}\n+\t\t}\n+\t} elseif [file exists {gitdir}] {\n+\t\tif {[catch {\n+\t\t\tset fd_gitdir [open {gitdir} {r}]\n+\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n+\t\t\tcatch {close $fd_gitdir}\n+\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n+\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\tif {$::_gitdir ne $parent_gitdir} {\n+\t\t\t\tset worktree {}\n+\t\t\t}\n+\t\t}]} {\n+\t\t\tcatch {close $fd_gitdir}\n+\t\t\tset worktree {}\n \t\t}\n \t}\n-\treturn $ok\n+\treturn $worktree\n }\n \n proc is_gitvars_error {err} {\n@@ -1155,62 +1182,76 @@ proc unset_gitdir_vars {} {\n \tcatch {unset env(GIT_WORK_TREE)}\n }\n \n+# find repository.\n+set _gitdir {}\n+if {[is_enabled gitdir_discovery]} {\n+\tif {[catch {\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n+\t} err]} {\n+\t\tif {[is_gitvars_error $err]} {\n+\t\t\texit 1\n+\t\t}\n+\t\tset _gitdir {}\n+\t}\n+}\n+\n set picked 0\n-proc pick_repo {} {\n+if {$_gitdir eq {} && [is_enabled picker]} {\n \tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n-\tchoose_repository::pick\n-\tset _gitdir [git rev-parse --absolute-git-dir]\n-\tset _prefix {}\n+\tif {![choose_repository::pick]} {\n+\t\texit 1\n+\t}\n+\tif {[catch {\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n+\t} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n+\t}\n \tset picked 1\n }\n \n-# run repository picker if explicitly requested\n-switch -- $subcommand {\n-\tpick {\n-\t\tpick_repo\n-\t\tset subcommand gui\n-\t\tset run_picker_on_error 0\n-\t}\n-}\n-\n-# find repository.\n-if {[catch {\n-\tset _gitdir [git rev-parse --absolute-git-dir]\n-} err]} {\n-\tif {[is_gitvars_error $err]} {\n-\t\texit 1\n-\t}\n-\tif {$run_picker_on_error} {\n-\t\tpick_repo\n-\t} else {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n-\t\texit 1\n-\t}\n+if {$_gitdir eq {}} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n+\texit 1\n }\n \n # find worktree, continue without if not required\n if {[catch {\n \tset _gitworktree [git rev-parse --show-toplevel]\n \tset _prefix [git rev-parse --show-prefix]\n-\tcd $_gitworktree\n } err]} {\n \tif {[is_gitvars_error $err]} {\n \t\texit 1\n \t}\n \tset _gitworktree {}\n \tset _prefix {}\n-\tif {[is_enabled bare]} {\n-\t\tcd $_gitdir\n-\t} elseif {![is_parent_worktree]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n-\t\texit 1\n+}\n+\n+if {[is_bare]} {\n+\t# Maybe we are in an embedded or worktree specific gitdir\n+\tif {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {\n+\t\tset _prefix {}\n \t}\n }\n \n+if {![is_bare]} {\n+\tif {[catch {\n+\t\tcd $_gitworktree\n+\t} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Cannot change to discovered worktree: \"] \\\n+\t\t\t\"$_gitworktree\" \"\\n\\n$err\"]\n+\t\texit 1;\n+\t}\n+} elseif {![is_enabled bare]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n+\texit 1\n+}\n+\n # repository and worktree config are complete, export them\n set_gitdir_vars\n \n@@ -1229,8 +1270,6 @@ if {$hashalgorithm eq \"sha1\"} {\n load_config 0\n apply_config\n \n-\n-# Derive a human-readable repository name\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -2035,7 +2074,7 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitdir _gitworktree\n+\tglobal _gitworktree\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n@@ -2045,8 +2084,6 @@ proc do_gitk {revs {is_submodule false}} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find gitk in PATH\"]\n \t} else {\n-\t\tglobal env\n-\n \t\tset pwd [pwd]\n \n \t\tif {!$is_submodule} {\n@@ -2105,9 +2142,6 @@ proc do_git_gui {} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find git gui in PATH\"]\n \t} else {\n-\t\tglobal env\n-\t\tglobal _gitdir _gitworktree\n-\n \t\t# see note in do_gitk about unsetting these vars when\n \t\t# running tools in a submodule\n \t\tunset_gitdir_vars\n@@ -2992,77 +3026,135 @@ proc normalize_relpath {path} {\n \tif {$elements ne {}} {\n \t\treturn [eval file join $elements]\n \t} else {\n-\t\treturn {}\n+\t\treturn {./}\n \t}\n }\n \n+proc find_path_type {head path} {\n+\tif {$path eq {./}} {\n+\t\t# the root-tree exists in every rev, ls-tree gives data on the contents,\n+\t\t# not the type of tree itself. So, if the rev exists, return {tree}\n+\t\tif {[catch {set objtype [git ls-tree $head]}]} {\n+\t\t\tset objtype {}\n+\t\t} else {\n+\t\t\tset objtype {tree}\n+\t\t}\n+\t} else {\n+\t\t# test that the path exists in head, ls-tree gives info on the path only\n+\t\tif {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {\n+\t\t\tset objtype {}\n+\t\t}\n+\t}\n+\treturn $objtype\n+}\n+\n # -- Not a normal commit type invocation?  Do that instead!\n #\n switch -- $subcommand {\n browser -\n blame {\n \tif {$subcommand eq \"blame\"} {\n-\t\tset subcommand_args {[--line=<num>] rev? path}\n+\t\tset subcommand_args {[--line=<num>] <[rev] [--] filename | [--] filename rev>}\n+\t\tset required_objtype blob\n \t} else {\n-\t\tset subcommand_args {rev? path}\n+\t\tset subcommand_args {<[rev] [--] directory | [--] directory rev>}\n+\t\tset required_objtype tree\n \t}\n-\tif {$argv eq {}} usage\n-\tset head {}\n-\tset path {}\n-\tset jump_spec {}\n+\tset maxargs [llength $subcommand_args]\n \tset nargs [llength $argv]\n-\tif {$nargs < 1} {\n-\t\tusage\n-\t}\n-\tset argn 0\n-\tforeach a $argv {\n-\t\tset argn [expr {$argn + 1}]\n+\tif {$nargs < 1 || $nargs > $maxargs} usage\n+\tset head {}\n+\tset althead {}\n+\tset path {}\n+\tset altpath {}\n+\tset canswap 1\n+\tset jump_spec {}\n \n-\t\tif {$argn < $nargs} {\n-\t\t\t# revision or line number\n-\t\t\tif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n-\t\t\t\tset jump_spec [list $lnum]\n+\t# assume: [--line=num] [head] [--] path as the possible arguments, in order.\n+\t# head and path may need a swap later.\n+\tfor {set iarg 0} {$iarg < $nargs} {incr iarg} {\n+\t\tset arg [lindex $argv $iarg]\n+\t\tif {$arg eq {--}} {\n+\t\t\t# next arg is the path, prevent or FORCE swap?\n+\t\t\tif {$iarg == $nargs - 2} {\n+\t\t\t\tset canswap 0\n+\t\t\t} elseif {$iarg == $nargs - 3} {\n+\t\t\t\tset canswap 2\n \t\t\t} else {\n-\t\t\t\tset head $a\n+\t\t\t\tusage\n \t\t\t}\n+\t\t} elseif {[regexp {^--line=(\\d+)$} $arg arg lnum]} {\n+\t\t\t# --line can only be the first arg\n+\t\t\tif {$iarg != 0 || $maxargs < 4} usage\n+\t\t\tset jump_spec [list $lnum]\n+\t\t} elseif {$iarg == $nargs - 1} {\n+\t\t\t# assume final argument is path\n+\t\t\tset path [normalize_relpath [file join $_prefix $arg]]\n+\t\t\tset althead $arg\n+\t\t} elseif {$head eq {}} {\n+\t\t\t# assume the other argument is head\n+\t\t\tset head $arg\n+\t\t\tset altpath [normalize_relpath [file join $_prefix $arg]]\n \t\t} else {\n-\t\t\tset path [normalize_relpath $a]\n+\t\t\tusage\n \t\t}\n \t}\n \n+\t# no swapping allowed if head not given, use current branch (HEAD)\n \tif {$head eq {}} {\n \t\tload_current_branch\n \t\tset head $current_branch\n-\t} else {\n-\t\tif {[regexp [string map \"@@ [expr $hashlength - 1]\" {^[0-9a-f]{1,@@}$}] $head]} {\n-\t\t\tif {[catch {\n-\t\t\t\tset head [git rev-parse --verify $head]\n-\t\t\t} err]} {\n-\t\t\t\tif {[tk windowingsystem] eq \"win32\"} {\n-\t\t\t\t\ttk_messageBox -icon error -title [mc Error] -message $err\n-\t\t\t\t} else {\n-\t\t\t\t\tputs stderr $err\n-\t\t\t\t}\n-\t\t\t\texit 1\n-\t\t\t}\n+\t\tset canswap 0\n+\t}\n+\n+\t# -- before \"rev\" arg means we got -- path head\n+\tif {$canswap == 2} {\n+\t\tset head $althead\n+\t\tset path $altpath\n+\t\tset canswap 0\n+\t}\n+\n+\tset objtype [find_path_type $head $path]\n+\tif {$objtype eq {} && $canswap} {\n+\t\tset objtype [find_path_type $althead $altpath]\n+\t\tif {$objtype ne {}} {\n+\t\t\tset head $althead\n+\t\t\tset path $altpath\n \t\t}\n-\t\tset current_branch $head\n+\t}\n+\tset current_branch $head\n+\n+\t# check that path exists in head, and objtype matches need\n+\tif {$objtype ne $required_objtype} {\n+\t\tswitch -- $required_objtype {\n+\t\t\ttree {set err [strcat \\\n+\t\t\t\t[mc \"'%s' is not a directory in rev '%s'\" $path $head]]}\n+\t\t\tblob {set err [strcat \\\n+\t\t\t\t[mc \"'%s' is not a filename in rev '%s'\" $path $head]]}\n+\t\t}\n+\t\tif {[tk windowingsystem] eq \"win32\"} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup $err\n+\t\t} else {\n+\t\t\tputs stderr $err\n+\t\t}\n+\t\texit 1\n \t}\n \n \twm deiconify .\n \tswitch -- $subcommand {\n \tbrowser {\n-\t\tif {$jump_spec ne {}} usage\n \t\tbrowser::new $head $path\n \t}\n-\tblame   {\n+\tblame {\n \t\tblame::new $head $path $jump_spec\n \t}\n \t}\n \treturn\n }\n citool -\n-gui {\n+gui -\n+pick {\n \tif {[llength $argv] != 0} {\n \t\tusage\n \t}\ndiff --git a/lib/choose_repository.tcl b/lib/choose_repository.tcl\nindex 7e1462a20c..4b06afee93 100644\n--- a/lib/choose_repository.tcl\n+++ b/lib/choose_repository.tcl\n@@ -15,7 +15,7 @@ field w_recentlist ; # Listbox containing recent repositories\n field w_localpath  ; # Entry widget bound to local_path\n \n field done              0 ; # Finished picking the repository?\n-field clone_ok      false ; # clone succeeeded\n+field pick_ok           0 ; # true if repo pick/clone succeeded\n field local_path       {} ; # Where this repository is locally\n field origin_url       {} ; # Where we are cloning from\n field origin_name  origin ; # What we shall call 'origin'\n@@ -220,6 +220,8 @@ constructor pick {} {\n \tif {$top eq {.}} {\n \t\teval destroy [winfo children $top]\n \t}\n+\n+\treturn $pick_ok\n }\n \n method _center {} {\n@@ -327,8 +329,7 @@ method _git_init {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir .git\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \treturn 1\n }\n \n@@ -409,6 +410,7 @@ method _do_new2 {} {\n \tif {![_git_init $this]} {\n \t\treturn\n \t}\n+\tset pick_ok 1\n \tset done 1\n }\n \n@@ -621,7 +623,7 @@ method _do_clone2 {} {\n \t}\n \n \ttkwait variable @done\n-\tif {!$clone_ok} {\n+\tif {!$pick_ok} {\n \t\terror_popup [mc \"Clone failed.\"]\n \t\treturn\n \t}\n@@ -632,18 +634,12 @@ method _do_clone2_done {ok} {\n \tif {$ok} {\n \t\tif {[catch {\n \t\t\tcd $local_path\n-\t\t\tset ::_gitdir .git\n-\t\t\tset ::_prefix {}\n \t\t\t_append_recentrepos [pwd]\n \t\t} err]} {\n \t\t\tset ok 0\n \t\t}\n \t}\n-\tif {!$ok} {\n-\t\tset ::_gitdir {}\n-\t\tset ::_prefix {}\n-\t}\n-\tset clone_ok $ok\n+\tset pick_ok $ok\n \tset done 1\n }\n \n@@ -721,8 +717,7 @@ method _do_open2 {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir $actualgit\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \tset done 1\n }\n \n-- \n2.54.0.99.14\n\n"},{"id":"543750","messageId":"20260520202411.108764-2-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:00Z","receivedAt":"2026-05-20T20:24:33Z","isPatch":true,"body":"git-gui unconditionally exports _gitdir as GIT_DIR, and _gitworktree as\nGIT_WORK_TREE, to the environment, and furthermore unconditionally\nunsets these environment variables in many\n\ngit gui must have a repository, so _gitdir can never be empty and its\nexport is always valid if repository discovery completes successfully.\n\ngit gui might not find a worktree, so _gitworktree can be empty. While\nhaving no worktree is valid for blame/browser subcommands, exporting\nGIT_WORK_TREE=<empty> is not valid. Rather, an empty GIT_WORK_TREE\nraises errors in git builtins, for instance 'git branch --show-current'\nas used by git, and causes breakage. This is one cause of git blame /\ngit browser not working without a worktree.\n\nA user may set GIT_DIR and/or GIT_WORK_TREE to override git's normal\ndiscovery rules, including repository configuration of core.worktree\nand/or worktree specific gitdirs. It is always safe to export the\nabsolute pathnames of the discovered values, even though they may not be\nneeded. However, the gitdir might not be found from the worktree without\nGIT_DIR being set. Furthermore, the worktree defined by the discovered\ngitdir might be overridden by GIT_WORK_TREE set before git-gui started.\nSo, it is also sometimes necessary that one or both of these variables\nis set.\n\nSo, let's provide two procs, one to unset GIT_DIR / GIT_WORK_TREE if\nthey are set, one to set GIT_DIR and, if not empty, GIT_WORK_TREE,  so\nall call sites do the same thing, and problems with _gitworktree == {}\nare avoided.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 39 +++++++++++++++++++++++----------------\n 1 file changed, 23 insertions(+), 16 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 23fe76e498..4ba25da7b6 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1122,6 +1122,22 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc set_gitdir_vars {} {\n+\tglobal _gitdir _gitworktree env\n+\tif {$_gitdir ne {}} {\n+\t\tset env(GIT_DIR) $_gitdir\n+\t}\n+\tif {$_gitworktree ne {}} {\n+\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t}\n+}\n+\n+proc unset_gitdir_vars {} {\n+\tglobal env\n+\tcatch {unset env(GIT_DIR)}\n+\tcatch {unset env(GIT_WORK_TREE)}\n+}\n+\n set picked 0\n if {[catch {\n \t\tset _gitdir $env(GIT_DIR)\n@@ -1207,8 +1223,8 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n+# Export the final paths\n+set_gitdir_vars\n \n ######################################################################\n ##\n@@ -2007,7 +2023,7 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitdir _gitworktree\n+\tglobal _gitworktree\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n@@ -2017,8 +2033,6 @@ proc do_gitk {revs {is_submodule false}} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find gitk in PATH\"]\n \t} else {\n-\t\tglobal env\n-\n \t\tset pwd [pwd]\n \n \t\tif {!$is_submodule} {\n@@ -2050,13 +2064,11 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t# TODO we could make life easier (start up faster?) for gitk\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n-\t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n+\t\t\tunset_gitdir_vars\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2079,21 +2091,16 @@ proc do_git_gui {} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find git gui in PATH\"]\n \t} else {\n-\t\tglobal env\n-\t\tglobal _gitdir _gitworktree\n-\n \t\t# see note in do_gitk about unsetting these vars when\n \t\t# running tools in a submodule\n-\t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n+\t\tunset_gitdir_vars\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n \n \t\tsafe_exec_bg [concat $exe gui]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.54.0.99.14\n\n"},{"id":"543751","messageId":"20260520202411.108764-3-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 02/11] git-gui: return status from choose_repository::pick","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:01Z","receivedAt":"2026-05-20T20:24:35Z","isPatch":true,"body":"The repository picker (choose_repository::pick) on success always\nreturns with the current directory at the root of the selected worktree,\nand with the global variable _gitdir holding the name of the git\nrepository, possibly as a relative path. On failure, _gitdir = {}. If\nthe selection was from the \"recent\" list, no validation has occurred.\n\nThere are too many side effects in this interface. Note that the picker\nonly supports worktrees with a .git entry in the worktree root, so git\nrepository and worktree discovery will work starting in the current\ndirectory on return. So, let's change pick to return a 0/1 value, 1\nmeaning a worktreee + repo was selected and the current directory is the\nworktree root, and leave validation and setting of _gitdir,\n_gitworktree, and _prefix to the caller. Note: pick actually does not\nreturn if something was not selected, rather it terminates git-gui.\nBut, let's pretend at the call site that pick returns 0/false instead.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh                | 10 ++++++++--\n lib/choose_repository.tcl | 21 ++++++++-------------\n 2 files changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 4ba25da7b6..4a736190a9 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1151,10 +1151,16 @@ if {[catch {\n \t} err]} {\n \tload_config 1\n \tapply_config\n-\tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n+\tif {![choose_repository::pick]} {\n \t\texit 1\n \t}\n+\tif {[catch {\n+\t\tset _gitdir [git rev-parse --git-dir]\n+\t} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n+\t}\n+\tset _prefix {}\n \tset picked 1\n }\n \ndiff --git a/lib/choose_repository.tcl b/lib/choose_repository.tcl\nindex 7e1462a20c..4b06afee93 100644\n--- a/lib/choose_repository.tcl\n+++ b/lib/choose_repository.tcl\n@@ -15,7 +15,7 @@ field w_recentlist ; # Listbox containing recent repositories\n field w_localpath  ; # Entry widget bound to local_path\n \n field done              0 ; # Finished picking the repository?\n-field clone_ok      false ; # clone succeeeded\n+field pick_ok           0 ; # true if repo pick/clone succeeded\n field local_path       {} ; # Where this repository is locally\n field origin_url       {} ; # Where we are cloning from\n field origin_name  origin ; # What we shall call 'origin'\n@@ -220,6 +220,8 @@ constructor pick {} {\n \tif {$top eq {.}} {\n \t\teval destroy [winfo children $top]\n \t}\n+\n+\treturn $pick_ok\n }\n \n method _center {} {\n@@ -327,8 +329,7 @@ method _git_init {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir .git\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \treturn 1\n }\n \n@@ -409,6 +410,7 @@ method _do_new2 {} {\n \tif {![_git_init $this]} {\n \t\treturn\n \t}\n+\tset pick_ok 1\n \tset done 1\n }\n \n@@ -621,7 +623,7 @@ method _do_clone2 {} {\n \t}\n \n \ttkwait variable @done\n-\tif {!$clone_ok} {\n+\tif {!$pick_ok} {\n \t\terror_popup [mc \"Clone failed.\"]\n \t\treturn\n \t}\n@@ -632,18 +634,12 @@ method _do_clone2_done {ok} {\n \tif {$ok} {\n \t\tif {[catch {\n \t\t\tcd $local_path\n-\t\t\tset ::_gitdir .git\n-\t\t\tset ::_prefix {}\n \t\t\t_append_recentrepos [pwd]\n \t\t} err]} {\n \t\t\tset ok 0\n \t\t}\n \t}\n-\tif {!$ok} {\n-\t\tset ::_gitdir {}\n-\t\tset ::_prefix {}\n-\t}\n-\tset clone_ok $ok\n+\tset pick_ok $ok\n \tset done 1\n }\n \n@@ -721,8 +717,7 @@ method _do_open2 {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir $actualgit\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \tset done 1\n }\n \n-- \n2.54.0.99.14\n\n"},{"id":"543752","messageId":"20260520202411.108764-5-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 04/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:03Z","receivedAt":"2026-05-20T20:24:36Z","isPatch":true,"body":"git-gui attempts to use env(GIT_DIR) directly as the git repository,\naccepting GIT_DIR if it is a directory. Only if that fails is git\nrev-parse used to discover the repository.  But, this avoids all of\ngit-core's validity checking on a repository, thus possibly deferring an\nerror to a later step, possibly unexpected. Repository validation should\nbe part of initial setup so that later processing does not need error\ntrapping for configuration errors.\n\nLet's just invoke rev-parse so all error checking is done.\n\nWhile here, let's cleanup the error handling.\n\nStop if an error occurs and the user set GIT_DIR or GIT_WORK_TREE.\nUse of either or both of those variables is supported by git, but their\nuse also means the user has taken responsibility that they are correct,\nso a failure is something the user must address.\n\nOtherwise on error, continue the existing behavior and show the\nrepository picker. But, let's move the possible invocation of\nrepository_chooser::pick to a separate code block. This permits adding\nseparate conditions on using pick indepent of repository discovery, and\nwill be exploited later in the series.  Note that the picker always\nreturns with the current directory in the root of a worktree with the\ngit repository is in the .git subdirectory.  The variable \"picked\" is\nused by git-gui to automatically execute the \"Explore Working Copy\" menu\nitem after the repository picker is run.  This is controlled by config\nvariable gui.autoexplore, and happens after all discovery is complete.\n\nRemove a later check on whether _gitdir is a directory: that code\ncannot be reached without rev-parse already validating the repository.\n\n_prefix should not be set before worktree discovery: the prefix is only\nknown after the worktree is found, and at this point we have only\ndiscovered the repository. This is true even when running the repository\npicker: that option provides a list of prior selections, and does no\nvalidation on the list beyond checking that the directories exist.  For\nnow, just initialize _prefix along with other global variables.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 48 +++++++++++++++++++++++++++++++++---------------\n 1 file changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 233c975786..c61a6cbd8f 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -374,6 +374,7 @@ set _gitdir {}\n set _gitworktree {}\n set _isbare {}\n set _githtmldir {}\n+set _prefix {}\n set _reponame {}\n set _shellpath {@@SHELL_PATH@@}\n \n@@ -1122,6 +1123,24 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc is_gitvars_error {err} {\n+\tset havevars 0\n+\tset GIT_DIR {}\n+\tset GIT_WORK_TREE {}\n+\tcatch {set GIT_DIR $::env(GIT_DIR); set havevars 1}\n+\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE) ; set havevars 1}\n+\n+\tif {$havevars} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Invalid configuration:\"] \\\n+\t\t   \"\\n\" \"GIT_DIR: \" $GIT_DIR \\\n+\t\t   \"\\n\" \"GIT_WORK_TREE: \" $GIT_WORK_TREE \\\n+\t\t\t\"\\n\\n$err\"]\n+\t\treturn 1\n+\t}\n+\treturn 0\n+}\n+\n proc set_gitdir_vars {} {\n \tglobal _gitdir _gitworktree env\n \tif {$_gitdir ne {}} {\n@@ -1138,17 +1157,22 @@ proc unset_gitdir_vars {} {\n \tcatch {unset env(GIT_WORK_TREE)}\n }\n \n-set picked 0\n-if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n+# find repository.\n+set _gitdir {}\n+if {$_gitdir eq {}} {\n+\tif {[catch {\n \t\tset _gitdir [git rev-parse --absolute-git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n+\t\tif {[is_gitvars_error $err]} {\n+\t\t\texit 1\n+\t\t}\n+\t\tset _gitdir {}\n+\t}\n+}\n+\n+set picked 0\n+if {$_gitdir eq {}} {\n+\tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n \tif {![choose_repository::pick]} {\n@@ -1160,7 +1184,6 @@ if {[catch {\n \t\tcatch {wm withdraw .}\n \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n \t}\n-\tset _prefix {}\n \tset picked 1\n }\n \n@@ -1175,11 +1198,6 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n-- \n2.54.0.99.14\n\n"},{"id":"543753","messageId":"20260520202411.108764-6-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 05/11] git-gui: simplify [is_bare] to report if a worktree is known","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:04Z","receivedAt":"2026-05-20T20:24:37Z","isPatch":true,"body":"git-gui includes proc is_bare, used in several places to make decisions\non whether a worktree exists, but also in discovery to tell if a\nworktree can be supported.\n\nBut, is_bare is out of date with regard to multiple worktrees, safe\nrepository guards, and possibly other relevant features known to git\nrev-parse. Also, is_bare caches its result on the first call, so is not\nuseful if a later step in the discovery process finds a worktree.\n\nSo, simplify is_bare to report whether git-gui has a worktree or is\nworking only from a repository.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 25 +------------------------\n 1 file changed, 1 insertion(+), 24 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex c61a6cbd8f..936c309e59 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -372,7 +372,6 @@ if {[tk windowingsystem] eq \"aqua\"} {\n set _appname {Git Gui}\n set _gitdir {}\n set _gitworktree {}\n-set _isbare {}\n set _githtmldir {}\n set _prefix {}\n set _reponame {}\n@@ -524,29 +523,7 @@ proc get_config {name} {\n }\n \n proc is_bare {} {\n-\tglobal _isbare\n-\tglobal _gitdir\n-\tglobal _gitworktree\n-\n-\tif {$_isbare eq {}} {\n-\t\tif {[catch {\n-\t\t\tset _bare [git rev-parse --is-bare-repository]\n-\t\t\tswitch  -- $_bare {\n-\t\t\ttrue { set _isbare 1 }\n-\t\t\tfalse { set _isbare 0}\n-\t\t\tdefault { throw }\n-\t\t\t}\n-\t\t}]} {\n-\t\t\tif {[is_config_true core.bare]\n-\t\t\t\t|| ($_gitworktree eq {}\n-\t\t\t\t\t&& [lindex [file split $_gitdir] end] ne {.git})} {\n-\t\t\t\tset _isbare 1\n-\t\t\t} else {\n-\t\t\t\tset _isbare 0\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn $_isbare\n+\treturn [expr {$::_gitworktree eq {}}]\n }\n \n ######################################################################\n-- \n2.54.0.99.14\n\n"},{"id":"543754","messageId":"20260520202411.108764-7-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 06/11] git-gui: use git rev-parse for worktree discovery","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:05Z","receivedAt":"2026-05-20T20:24:38Z","isPatch":true,"body":"git gui uses a combination of tcl code and git invocations to determine\nthe worktree and the location with respect to the worktree root\n(_prefix). But, git rev-parse provides all of this information directly,\nand assures full error and configuration checking are done by git\nitself. The entirety of discovery in normal configurations involves\n\n\tgit rev-parse --show-toplevel (gets worktree root)\n\tgit rev-parse --show-prefix (shows location wrt the root)\n\nAn error thrown on either of these lines means the worktree discovered\nby git is unusable, or git did not discover a worktree because the\ncurrent directory is inside the repository. If the user has defined\nGIT_DIR or GIT_WORK_TREE, this is a user configuration error and git-gui\nshould stop.\n\nOtherwise, the blame or browser subcommands can be used without a\nworktree.\n\nA separate error might occur when changing to the root of the discovered\nworktree. The cause would be file system related and completely outside\nof git's control. So, the final \"cd $worktree_root\" is separately\ntrapped.\n\nDiscovery of the repository and the worktree must be guarded to trap\nerrors: the intent is that any configuration problems are caught during\ndiscovery, and later processing need not include error trapping and\nrecovery. So, move all worktree discovery code to be immediately after\nrepository discovery.\n\nThis does move configuration loading to occur after worktree discovery\nrather than before. None of the code executed in worktree discovery has\nany option controlled by a git-gui configuration variable, so no impact\nis expected. git itself will always read the repository configuration,\nincluding worktree specific configuration data if that exists, so this\nis unaffected by when git-gui loads its own config data, and we cannot\nbe sure the full worktree dependent configuration can be loaded before\nfull discovery is complete.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 64 +++++++++++++++++++++++++-----------------------------\n 1 file changed, 30 insertions(+), 34 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 936c309e59..8fe25fe188 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1164,6 +1164,36 @@ if {$_gitdir eq {}} {\n \tset picked 1\n }\n \n+# find worktree, continue without if not required\n+if {[catch {\n+\tset _gitworktree [git rev-parse --show-toplevel]\n+\tset _prefix [git rev-parse --show-prefix]\n+} err]} {\n+\tif {[is_gitvars_error $err]} {\n+\t\texit 1\n+\t}\n+\tset _gitworktree {}\n+\tset _prefix {}\n+}\n+\n+if {![is_bare]} {\n+\tif {[catch {\n+\t\tcd $_gitworktree\n+\t} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Cannot change to discovered worktree: \"] \\\n+\t\t\t\"$_gitworktree\" \"\\n\\n$err\"]\n+\t\texit 1;\n+\t}\n+} elseif {![is_enabled bare]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n+\texit 1\n+}\n+\n+# repository and worktree config are complete, export them\n+set_gitdir_vars\n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n set hashalgorithm [git rev-parse --show-object-format]\n if {$hashalgorithm eq \"sha1\"} {\n@@ -1179,37 +1209,6 @@ if {$hashalgorithm eq \"sha1\"} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n-\n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n-\t\texit 1\n-\t}\n-\tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n-\t}\n-\tif {[catch {cd $_gitworktree} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-}\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1217,9 +1216,6 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-# Export the final paths\n-set_gitdir_vars\n-\n ######################################################################\n ##\n ## global init\n-- \n2.54.0.99.14\n\n"},{"id":"543755","messageId":"20260520202411.108764-8-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:06Z","receivedAt":"2026-05-20T20:24:39Z","isPatch":true,"body":"git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n2010-01-23), has had the intent to allow starting from inside a\nrepository, then switching to the parent directory if that is a valid\nworktree.\n\nThis certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n--show-toplevel without a worktree an error\", 2019-11-19) in git, but\nbreaking this git-gui feature was unintentional.\n\nThere are (at least) 3 cases where the gitdir can tell us where the\nworktree is, and we would like all to work:\n\n- core.worktree is set, and points to a valid worktree. This is already\n  handled  by git rev-parse --show-toplevel, even when not in the worktree.\n  There is nothing more to do in this case.\n\n- the gitdir is embedded in a worktree as subdirectory .git. The parent\n  is (or at least should be) a valid worktree. This worked long ago.\n\n- the gitdir is a worktree specific directory (under\n  <mainrepo>/worktrees/worktree_name), within which there is a file\n  \"gitdir\" pointing to .git in the worktree. git gui never learned to\n  handle this case.\n\nLet's handle the latter two cases. Always check that the discovered\nworktree is valid and points to the already discovered gitdir according\nto git rev-parse. This avoids issues that may arise because we are\ndiscovering from the gitdir up, rather than the worktree down, and file\nsystem non-posix behavior or misconfiguration of git might cause\nconfusion.  For instance, a manually moved worktree might not be where\nthe gitdir points.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 42 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 42 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 8fe25fe188..aeb7ed3548 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1100,6 +1100,41 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc find_worktree_from_gitdir {} {\n+\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree.\n+\t# Assure parent is a worktree and using the git repository already discovered.\n+\t# Also, handle case of being in a worktree's gitdir, where file \"gitdir\" points to\n+\t# gitlink file .git in the real worktree.\n+\tset worktree {}\n+\tif {[file tail $::_gitdir] eq {.git}} {\n+\t\tif {[catch {\n+\t\t\tset gitdir_parent [file dirname $::_gitdir]\n+\t\t\tset worktree [git -C $gitdir_parent rev-parse --show-toplevel]\n+\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\tif {$::_gitdir ne $parent_gitdir} {\n+\t\t\t\tset worktree {}\n+\t\t\t}\n+\t\t}]} {\n+\t\t\tset worktree {}\n+\t\t}\n+\t} elseif [file exists {gitdir}] {\n+\t\tif {[catch {\n+\t\t\tset fd_gitdir [open {gitdir} {r}]\n+\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n+\t\t\tcatch {close $fd_gitdir}\n+\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n+\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\tif {$::_gitdir ne $parent_gitdir} {\n+\t\t\t\tset worktree {}\n+\t\t\t}\n+\t\t}]} {\n+\t\t\tcatch {close $fd_gitdir}\n+\t\t\tset worktree {}\n+\t\t}\n+\t}\n+\treturn $worktree\n+}\n+\n proc is_gitvars_error {err} {\n \tset havevars 0\n \tset GIT_DIR {}\n@@ -1176,6 +1211,13 @@ if {[catch {\n \tset _prefix {}\n }\n \n+if {[is_bare]} {\n+\t# Maybe we are in an embedded or worktree specific gitdir\n+\tif {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {\n+\t\tset _prefix {}\n+\t}\n+}\n+\n if {![is_bare]} {\n \tif {[catch {\n \t\tcd $_gitworktree\n-- \n2.54.0.99.14\n\n"},{"id":"543756","messageId":"20260520202411.108764-4-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 03/11] git-gui: use --absolute-git-dir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:02Z","receivedAt":"2026-05-20T20:24:39Z","isPatch":true,"body":"git-gui uses git rev-parse --git-dir to get the pathname of the\ndiscovered git repository. The returned value can be relative, and is\n'.' if the current directory is the top of the repository directory\nitself.  git-gui has code to change '.' to [pwd] in this case so that\nsubsequent logic runs.\n\nBut, git rev-parse supports --absolute-git-dir from fac60b8925\n(\"rev-parse: add option for absolute or relative path formatting\",\n2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\nthis more useful form is always available. Use --absolute-git-dir to\nalways get an absolute path, avoiding the need for other checks, and\ndelete the now unneeded code to fix a relative _gitdir.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 11 ++---------\n 1 file changed, 2 insertions(+), 9 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 4a736190a9..233c975786 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1146,7 +1146,7 @@ if {[catch {\n \t&& [catch {\n \t\t# beware that from the .git dir this sets _gitdir to .\n \t\t# and _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --git-dir]\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n \tload_config 1\n@@ -1155,7 +1155,7 @@ if {[catch {\n \t\texit 1\n \t}\n \tif {[catch {\n-\t\tset _gitdir [git rev-parse --git-dir]\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t} err]} {\n \t\tcatch {wm withdraw .}\n \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n@@ -1175,13 +1175,6 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-# we expand the _gitdir when it's just a single dot (i.e. when we're being\n-# run from the .git dir itself) lest the routines to find the worktree\n-# get confused\n-if {$_gitdir eq \".\"} {\n-\tset _gitdir [pwd]\n-}\n-\n if {![file isdirectory $_gitdir]} {\n \tcatch {wm withdraw .}\n \terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-- \n2.54.0.99.14\n\n"},{"id":"543757","messageId":"20260520202411.108764-9-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 08/11] git-gui: use HEAD as current branch when detached (bug fix)","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:07Z","receivedAt":"2026-05-20T20:24:40Z","isPatch":true,"body":"commit f87a36b697 (\"git-gui: use git-branch --show-current\", 2024-02-12)\nchanged git-gui to use git-branch to access refs, rather than directly\nreading files as doing the latter is not compatible with the reftable\nbackend. git branch --show-current reports an empty branch name when the\nhead is detached, and in this case load_current_branch needs to report\nHEAD using special case logic as it did prior to the above commit. Make\nit do so.\n\nThis addresses an issue with git-gui browser failing with a detached\nhead.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex aeb7ed3548..a72d8a59ec 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -648,6 +648,9 @@ proc load_current_branch {} {\n \n \tset current_branch [git branch --show-current]\n \tset is_detached [expr [string length $current_branch] == 0]\n+\tif {$is_detached} {\n+\t\tset current_branch {HEAD}\n+\t}\n }\n \n auto_load tk_optionMenu\n-- \n2.54.0.99.14\n\n"},{"id":"543758","messageId":"20260520202411.108764-10-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 09/11] git-gui: allow specifying path '.' to the browser","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:08Z","receivedAt":"2026-05-20T20:24:41Z","isPatch":true,"body":"Invoking \"git-gui browser rev .\" should show the file browser for the\ncommitish rev, starting at the current directory. When the current\ndirectory is the working tree root, this errors out in normalize_relpath\nbecause the '.' is removed, yielding an empty list as argument to [file\njoin ...]. The browser function demands \"./\" in this case, so make it\nso. (./ works on Windows as well because g4w accepts posix file\nnaming).\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex a72d8a59ec..d373457901 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -3007,7 +3007,11 @@ proc normalize_relpath {path} {\n \t\t}\n \t\tlappend elements $item\n \t}\n-\treturn [eval file join $elements]\n+\tif {$elements ne {}} {\n+\t\treturn [eval file join $elements]\n+\t} else {\n+\t\treturn {./}\n+\t}\n }\n \n # -- Not a normal commit type invocation?  Do that instead!\n-- \n2.54.0.99.14\n\n"},{"id":"543759","messageId":"20260520202411.108764-11-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 10/11] git-gui: adapt blame/browser parsing for bare operation","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:09Z","receivedAt":"2026-05-20T20:24:41Z","isPatch":true,"body":"git-gui's blame and browser subcommands do not work with bare\nrepositories, but they should per commit c52c94524b (\"git-gui: Allow\nblame/browser subcommands on bare repositories\", 2007-07-17). Assuming\nthat commit worked, something changed since reintroducing a hard-coded\ndependency upon a worktree.\n\nThe basic issue goes back to 3e45ee1ef2 (\"git-gui: Smarter command line\nparsing for browser, blame\", 2007-05-08), which seeks to implement\ncommand line parsing similar to git blame. That commit introduces\ndepencies upon the worktree to decide which argument is rev or path.\n\nLooking at builtin/blame.c in git around line 1120:\n\n\t * (1) if dashdash_pos != 0, it is either\n\t *     \"blame [revisions] -- <path>\" or\n\t *     \"blame -- <path> <rev>\"\n\t *\n\t * (2) otherwise, it is one of the two:\n\t *     \"blame [revisions] <path>\"\n\t *     \"blame <path> <rev>\"\n\nshows the clear intent: rev and path may be swapped in input so both\nmeanings must be tried, but -- may be used to designate which is the\npath forcing or precluding trying the swapped arguments.\n\nWith a worktree, git gui correctly swaps the arguments if the given path\nexists in the worktree. git blame does this using the git repository.\nBut, git-gui sometimes interprets the -- to have an exactly opposite\nmeaning:\n\n    git blame       Makefile gitgui-0.19.0       works\n    git gui blame   Makefile gitgui-0.19.0       works\n\n    git blame       -- Makefile gitgui-0.19.0    works\n    git gui blame   -- Makefile gitgui-0.19.0    works\n\n    git blame       Makefile -- gitgui-0.19.0    fails (correctly)\n    git gui blame   Makefile -- gitgui-0.19.0    works (should fail)\n\n    git blame       gitgui-0.19.0 -- Makefile    works (correctly)\n    git gui blame   gitgui-0.19.0 -- Makefile    fails (should work)\n\nIt is possible to patch the code to operate without a worktree, but this\nwill make the commands operate differently with and without a worktree,\nwon't fix the parsing issues above, and won't address the issues that\ncan arise when using a worktree to help decisions on a different rev\nwith file/directory conflicts, etc.\n\nSo, let's rework the parser so that it uses -- as does git blame, and\nuses git ls-tree to query the given revision for existence and type of\npath rather than basing this upon a possibly unrelated worktree. Also,\nabort early when the given path is not found, or does not match the need\n(file or directory). This fixes some current cases where git-gui will\nopen a window with no content, possibly also with an error message.\n\nThis does not change whether or how git-gui uses staged and unstaged\ncontent in the current worktree for blame display.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 151 ++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 87 insertions(+), 64 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex d373457901..ae609f86f1 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -3014,100 +3014,123 @@ proc normalize_relpath {path} {\n \t}\n }\n \n+proc find_path_type {head path} {\n+\tif {$path eq {./}} {\n+\t\t# the root-tree exists in every rev, ls-tree gives data on the contents,\n+\t\t# not the type of tree itself. So, if the rev exists, return {tree}\n+\t\tif {[catch {set objtype [git ls-tree $head]}]} {\n+\t\t\tset objtype {}\n+\t\t} else {\n+\t\t\tset objtype {tree}\n+\t\t}\n+\t} else {\n+\t\t# test that the path exists in head, ls-tree gives info on the path only\n+\t\tif {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {\n+\t\t\tset objtype {}\n+\t\t}\n+\t}\n+\treturn $objtype\n+}\n+\n # -- Not a normal commit type invocation?  Do that instead!\n #\n switch -- $subcommand {\n browser -\n blame {\n \tif {$subcommand eq \"blame\"} {\n-\t\tset subcommand_args {[--line=<num>] rev? path}\n+\t\tset subcommand_args {[--line=<num>] <[rev] [--] filename | [--] filename rev>}\n+\t\tset required_objtype blob\n \t} else {\n-\t\tset subcommand_args {rev? path}\n+\t\tset subcommand_args {<[rev] [--] directory | [--] directory rev>}\n+\t\tset required_objtype tree\n \t}\n-\tif {$argv eq {}} usage\n+\tset maxargs [llength $subcommand_args]\n+\tset nargs [llength $argv]\n+\tif {$nargs < 1 || $nargs > $maxargs} usage\n \tset head {}\n+\tset althead {}\n \tset path {}\n+\tset altpath {}\n+\tset canswap 1\n \tset jump_spec {}\n-\tset is_path 0\n-\tforeach a $argv {\n-\t\tset p [file join $_prefix $a]\n \n-\t\tif {$is_path || [file exists $p]} {\n-\t\t\tif {$path ne {}} usage\n-\t\t\tset path [normalize_relpath $p]\n-\t\t\tbreak\n-\t\t} elseif {$a eq {--}} {\n-\t\t\tif {$path ne {}} {\n-\t\t\t\tif {$head ne {}} usage\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n+\t# assume: [--line=num] [head] [--] path as the possible arguments, in order.\n+\t# head and path may need a swap later.\n+\tfor {set iarg 0} {$iarg < $nargs} {incr iarg} {\n+\t\tset arg [lindex $argv $iarg]\n+\t\tif {$arg eq {--}} {\n+\t\t\t# next arg is the path, prevent or FORCE swap?\n+\t\t\tif {$iarg == $nargs - 2} {\n+\t\t\t\tset canswap 0\n+\t\t\t} elseif {$iarg == $nargs - 3} {\n+\t\t\t\tset canswap 2\n+\t\t\t} else {\n+\t\t\t\tusage\n \t\t\t}\n-\t\t\tset is_path 1\n-\t\t} elseif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n-\t\t\tif {$jump_spec ne {} || $head ne {}} usage\n+\t\t} elseif {[regexp {^--line=(\\d+)$} $arg arg lnum]} {\n+\t\t\t# --line can only be the first arg\n+\t\t\tif {$iarg != 0 || $maxargs < 4} usage\n \t\t\tset jump_spec [list $lnum]\n+\t\t} elseif {$iarg == $nargs - 1} {\n+\t\t\t# assume final argument is path\n+\t\t\tset path [normalize_relpath [file join $_prefix $arg]]\n+\t\t\tset althead $arg\n \t\t} elseif {$head eq {}} {\n-\t\t\tif {$head ne {}} usage\n-\t\t\tset head $a\n-\t\t\tset is_path 1\n+\t\t\t# assume the other argument is head\n+\t\t\tset head $arg\n+\t\t\tset altpath [normalize_relpath [file join $_prefix $arg]]\n \t\t} else {\n \t\t\tusage\n \t\t}\n \t}\n-\tunset is_path\n-\n-\tif {$head ne {} && $path eq {}} {\n-\t\tif {[string index $head 0] eq {/}} {\n-\t\t\tset path [normalize_relpath $head]\n-\t\t\tset head {}\n-\t\t} else {\n-\t\t\tset path [normalize_relpath $_prefix$head]\n-\t\t\tset head {}\n-\t\t}\n-\t}\n \n+\t# no swapping allowed if head not given, use current branch (HEAD)\n \tif {$head eq {}} {\n \t\tload_current_branch\n-\t} else {\n-\t\tif {[regexp [string map \"@@ [expr $hashlength - 1]\" {^[0-9a-f]{1,@@}$}] $head]} {\n-\t\t\tif {[catch {\n-\t\t\t\t\tset head [git rev-parse --verify $head]\n-\t\t\t\t} err]} {\n-\t\t\t\tif {[tk windowingsystem] eq \"win32\"} {\n-\t\t\t\t\ttk_messageBox -icon error -title [mc Error] -message $err\n-\t\t\t\t} else {\n-\t\t\t\t\tputs stderr $err\n-\t\t\t\t}\n-\t\t\t\texit 1\n-\t\t\t}\n+\t\tset head $current_branch\n+\t\tset canswap 0\n+\t}\n+\n+\t# -- before \"rev\" arg means we got -- path head\n+\tif {$canswap == 2} {\n+\t\tset head $althead\n+\t\tset path $altpath\n+\t\tset canswap 0\n+\t}\n+\n+\tset objtype [find_path_type $head $path]\n+\tif {$objtype eq {} && $canswap} {\n+\t\tset objtype [find_path_type $althead $altpath]\n+\t\tif {$objtype ne {}} {\n+\t\t\tset head $althead\n+\t\t\tset path $altpath\n \t\t}\n-\t\tset current_branch $head\n+\t}\n+\tset current_branch $head\n+\n+\t# check that path exists in head, and objtype matches need\n+\tif {$objtype ne $required_objtype} {\n+\t\tswitch -- $required_objtype {\n+\t\t\ttree {set err [strcat \\\n+\t\t\t\t[mc \"'%s' is not a directory in rev '%s'\" $path $head]]}\n+\t\t\tblob {set err [strcat \\\n+\t\t\t\t[mc \"'%s' is not a filename in rev '%s'\" $path $head]]}\n+\t\t}\n+\t\tif {[tk windowingsystem] eq \"win32\"} {\n+\t\t\tcatch {wm withdraw .}\n+\t\t\terror_popup $err\n+\t\t} else {\n+\t\t\tputs stderr $err\n+\t\t}\n+\t\texit 1\n \t}\n \n \twm deiconify .\n \tswitch -- $subcommand {\n \tbrowser {\n-\t\tif {$jump_spec ne {}} usage\n-\t\tif {$head eq {}} {\n-\t\t\tif {$path ne {} && [file isdirectory $path]} {\n-\t\t\t\tset head $current_branch\n-\t\t\t} else {\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n-\t\t\t}\n-\t\t}\n \t\tbrowser::new $head $path\n \t}\n-\tblame   {\n-\t\tif {$head eq {} && ![file exists $path]} {\n-\t\t\tcatch {wm withdraw .}\n-\t\t\ttk_messageBox \\\n-\t\t\t\t-icon error \\\n-\t\t\t\t-type ok \\\n-\t\t\t\t-title [mc \"git-gui: fatal error\"] \\\n-\t\t\t\t-message [mc \"fatal: cannot stat path %s: No such file or directory\" $path]\n-\t\t\texit 1\n-\t\t}\n+\tblame {\n \t\tblame::new $head $path $jump_spec\n \t}\n \t}\n-- \n2.54.0.99.14\n\n"},{"id":"543760","messageId":"20260520202411.108764-12-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v2 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-20T20:24:10Z","receivedAt":"2026-05-20T20:24:42Z","isPatch":true,"body":"git-gui accepts subcommands blame | browser | citool, and assumes the\nsubcommand is 'gui' if none is actually given, But, git-gui also has a\nrepository picker (choose_repository::pick) that can create a new\nrepository + worktree, or choose an existing one, switch to that, and\nthe run the gui. The user has no direct control over invoking the\npicker, instead the picker is triggered by failure in the repository /\nworktree discovery process: this includes being started in a directory\nnot controlled by git, which is probably the intended use case.\n\nThe picker can appear when the user has no intention of creating a new\nworktree, and the user cannot use the picker to create a new worktree\ninside another.\n\nSo, add two explicit subcommands:\n    gui  - Run the gui if repository/worktree discovery succeeds, or die\n           with an error message, but never run the picker.\n    pick - First run the picker, regardless, then start the gui in\n           the chosen worktree.\n\nNothing in this changes the prior behavior, the alternates above must be\nexplicitly selected to see any change.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 25 +++++++++++++++++++++----\n 1 file changed, 21 insertions(+), 4 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex ae609f86f1..299c1a0292 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1024,6 +1024,8 @@ proc load_config {include_global} {\n ##\n ## feature option selection\n \n+enable_option picker\n+enable_option gitdir_discovery\n if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n \tunset _junk\n } else {\n@@ -1035,6 +1037,9 @@ if {$subcommand eq {gui.sh}} {\n if {$subcommand eq {gui} && [llength $argv] > 0} {\n \tset subcommand [lindex $argv 0]\n \tset argv [lrange $argv 1 end]\n+\tif {$subcommand eq {gui}} {\n+\t\tdisable_option picker\n+\t}\n }\n \n enable_option multicommit\n@@ -1050,6 +1055,7 @@ blame {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tdisable_option picker\n }\n citool {\n \tenable_option singlecommit\n@@ -1058,6 +1064,7 @@ citool {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tdisable_option picker\n \n \twhile {[llength $argv] > 0} {\n \t\tset a [lindex $argv 0]\n@@ -1080,6 +1087,9 @@ citool {\n \t\tset argv [lrange $argv 1 end]\n \t}\n }\n+pick {\n+\tdisable_option gitdir_discovery\n+}\n }\n \n ######################################################################\n@@ -1174,7 +1184,7 @@ proc unset_gitdir_vars {} {\n \n # find repository.\n set _gitdir {}\n-if {$_gitdir eq {}} {\n+if {[is_enabled gitdir_discovery]} {\n \tif {[catch {\n \t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t} err]} {\n@@ -1186,7 +1196,7 @@ if {$_gitdir eq {}} {\n }\n \n set picked 0\n-if {$_gitdir eq {}} {\n+if {$_gitdir eq {} && [is_enabled picker]} {\n \tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n@@ -1202,6 +1212,12 @@ if {$_gitdir eq {}} {\n \tset picked 1\n }\n \n+if {$_gitdir eq {}} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n+\texit 1\n+}\n+\n # find worktree, continue without if not required\n if {[catch {\n \tset _gitworktree [git rev-parse --show-toplevel]\n@@ -3137,14 +3153,15 @@ blame {\n \treturn\n }\n citool -\n-gui {\n+gui -\n+pick {\n \tif {[llength $argv] != 0} {\n \t\tusage\n \t}\n \t# fall through to setup UI for commits\n }\n default {\n-\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool}\\]\"\n+\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool|gui|pick}\\]\"\n \tif {[tk windowingsystem] eq \"win32\"} {\n \t\twm withdraw .\n \t\ttk_messageBox -icon error -message $err \\\n-- \n2.54.0.99.14\n\n"},{"id":"543768","messageId":"tencent_E13EB585242AD7C263B8B3B732A428465D09@qq.com","threadId":"65531","inReplyTo":"20260520202411.108764-8-mlevedahl@gmail.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-21T04:55:27Z","receivedAt":"2026-05-21T04:56:51Z","isPatch":true,"body":"On 5/21/26 4:24 AM, Mark Levedahl wrote:\n> +\t} elseif [file exists {gitdir}] {\n> +\t\tif {[catch {\n> +\t\t\tset fd_gitdir [open {gitdir} {r}]\n> +\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n> +\t\t\tcatch {close $fd_gitdir}\n> +\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n> +\t\t\t\tset worktree {}\n> +\t\t\t}\n> +\t\t}]} {\n> +\t\t\tcatch {close $fd_gitdir}\n> +\t\t\tset worktree {}\n> +\t\t}\n> +\t}\n\nThere is also an unaddressed issue: \nIn [file exists {gitdir}] and [open {gitdir} r], {gitdir} is a \nliteral string referring to a file named gitdir in the current \nworking directory. However, in the context of a linked worktree \n(created via git worktree add), the actual file path is \n$_gitdir/gitdir (e.g., .git/worktrees/<name>/gitdir). While the \ncurrent working directory could be anywhere (even inside the .git \ndirectory), $_gitdir is an absolute path pointing to that worktree's \ngitdir (e.g., /path/to/main/.git/worktrees/branch). The gitdir file \nresides within the $_gitdir directory and contains a relative path \nlike ../../.git/worktrees/branch. The current code logic will never \nlocate this file. \n\nAdditionally, [file exists {gitdir}] checks for the gitdir file in \nthe current working directory. Since the function has not yet \nswitched to $_gitdir when this check runs, it is almost impossible \nto find the file. Consequently, this logic never triggers, preventing \nlinked worktrees from being recognized. \n\nMaybe the identification of linked worktree should not directly look \nfor the gitdir file, but should check whether there is a.git file and \nits content points to... /.git/worktrees/... ? Anyways, using the \nliteral {gitdir} to search in the current directory lead to risks. \n\nShroom\n\n"},{"id":"543769","messageId":"tencent_407FE60B6954528497709B6CAD49018D120A@qq.com","threadId":"65531","inReplyTo":"20260520202411.108764-11-mlevedahl@gmail.com","subject":"Re: [PATCH v2 10/11] git-gui: adapt blame/browser parsing for bare operation","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-21T05:02:59Z","receivedAt":"2026-05-21T05:03:24Z","isPatch":true,"body":"On 5/21/26 4:24 AM, Mark Levedahl wrote: \n> +proc find_path_type {head path} {\n> +\tif {$path eq {./}} {\n> +\t\t# the root-tree exists in every rev, ls-tree gives data on the contents,\n> +\t\t# not the type of tree itself. So, if the rev exists, return {tree}\n> +\t\tif {[catch {set objtype [git ls-tree $head]}]} {\n> +\t\t\tset objtype {}\n> +\t\t} else {\n> +\t\t\tset objtype {tree}\n> +\t\t}\n> +\t} else {\n> +\t\t# test that the path exists in head, ls-tree gives info on the path only\n> +\t\tif {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {\n> +\t\t\tset objtype {}\n> +\t\t}\n> +\t}\n> +\treturn $objtype\n> +}\n\nIn v1, argument parsing relied on file exists within the worktree to \ndetermine if a path existed, without using ls-tree. In v2, the use of \ngit ls-tree seems to actually be intended to list directory contents, \nrather than querying the type of the path itself. \n\nIf $path is a directory (a tree object), git ls-tree outputs the \nobject type for every entry within that directory, one per line. \n\nThe variable objtype is assigned a multi-line string. When compared \nagainst \"tree\", the match fails, causing the function to return an \nempty string, which subsequently leads to an error. We can change to \n\"git cat-file -t\" or similiar approaches. \n\nShroom\n\n"},{"id":"543842","messageId":"bb38c5d4-b388-4eca-badd-69ec7ce67b90@gmail.com","threadId":"65531","inReplyTo":"tencent_407FE60B6954528497709B6CAD49018D120A@qq.com","subject":"Re: [PATCH v2 10/11] git-gui: adapt blame/browser parsing for bare operation","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-21T17:35:31Z","receivedAt":"2026-05-21T17:35:34Z","isPatch":true,"body":"\n\nOn 5/21/26 1:02 AM, Shroom Moo wrote:\n> On 5/21/26 4:24 AM, Mark Levedahl wrote: \n>> +proc find_path_type {head path} {\n>> +\tif {$path eq {./}} {\n>> +\t\t# the root-tree exists in every rev, ls-tree gives data on the contents,\n>> +\t\t# not the type of tree itself. So, if the rev exists, return {tree}\n>> +\t\tif {[catch {set objtype [git ls-tree $head]}]} {\n>> +\t\t\tset objtype {}\n>> +\t\t} else {\n>> +\t\t\tset objtype {tree}\n>> +\t\t}\n>> +\t} else {\n>> +\t\t# test that the path exists in head, ls-tree gives info on the path only\n>> +\t\tif {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {\n>> +\t\t\tset objtype {}\n>> +\t\t}\n>> +\t}\n>> +\treturn $objtype\n>> +}\n> In v1, argument parsing relied on file exists within the worktree to \n> determine if a path existed, without using ls-tree. In v2, the use of \n> git ls-tree seems to actually be intended to list directory contents, \n> rather than querying the type of the path itself. \n>\n> If $path is a directory (a tree object), git ls-tree outputs the \n> object type for every entry within that directory, one per line. \n>\n> The variable objtype is assigned a multi-line string. When compared \n> against \"tree\", the match fails, causing the function to return an \n> empty string, which subsequently leads to an error. We can change to \n> \"git cat-file -t\" or similiar approaches. \n>\n> Shroom\n>\ngit ls-tree $rev $path --format='%(objecttype)' gives the type of the object at $path in\n$rev, or an error. The types returned are \"tree\" for a directory, \"blob\" for a file. So\nthis gives definitive information if the object desired exists in the given rev, and is of\nthe right type. (We don't care about commits and tags, those cannot be blamed or browsed).\n\ngit-lstree $rev $dirname/  lists all of the objects in the directory, while git-lstree\n$rev $dirname (no trailing /) gives info on the directory itself. There is no name for the\nroot directory itself, all of its contents are listed. That is why the root './' is\nspecial case.\n\nAsking the worktree that is on version 33 about whether frotz is a directory in version 2\nis just asking for trouble, at best the worktree is authoritative for the checked out\nversion, but even then there can be uncommitted changed. In the root of git-gui, I get\n\n/git-gui.sh browser gitgui-0.9.0 Makefile\n'Makefile' is not a directory in rev 'gitgui-0.9.0'\n\nso the types of objects are being checked.\n\nMark\n\n"},{"id":"543843","messageId":"780a52d9-7119-4870-990a-16280005503d@gmail.com","threadId":"65531","inReplyTo":"tencent_E13EB585242AD7C263B8B3B732A428465D09@qq.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-21T17:45:41Z","receivedAt":"2026-05-21T17:45:43Z","isPatch":true,"body":"\n\nOn 5/21/26 12:55 AM, Shroom Moo wrote:\n> On 5/21/26 4:24 AM, Mark Levedahl wrote:\n>> +\t} elseif [file exists {gitdir}] {\n>> +\t\tif {[catch {\n>> +\t\t\tset fd_gitdir [open {gitdir} {r}]\n>> +\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n>> +\t\t\tcatch {close $fd_gitdir}\n>> +\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n>> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n>> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n>> +\t\t\t\tset worktree {}\n>> +\t\t\t}\n>> +\t\t}]} {\n>> +\t\t\tcatch {close $fd_gitdir}\n>> +\t\t\tset worktree {}\n>> +\t\t}\n>> +\t}\n> There is also an unaddressed issue: \n> In [file exists {gitdir}] and [open {gitdir} r], {gitdir} is a \n> literal string referring to a file named gitdir in the current \n> working directory. However, in the context of a linked worktree \n> (created via git worktree add), the actual file path is \n> $_gitdir/gitdir (e.g., .git/worktrees/<name>/gitdir). While the \n> current working directory could be anywhere (even inside the .git \n> directory), $_gitdir is an absolute path pointing to that worktree's \n> gitdir (e.g., /path/to/main/.git/worktrees/branch). The gitdir file \n> resides within the $_gitdir directory and contains a relative path \n> like ../../.git/worktrees/branch. The current code logic will never \n> locate this file. \nYou have to be in the particular worktree's gitdir for this to work. I there exists\n    worktrees/foo\n    worktrees/frotz\n    worktrees/bar\nWhich would we expore? The code above must be in foo, frotz, bar\n\nThe main worktree is found not from worktrees/*, but from the root of the gitdir.\n>\n> Additionally, [file exists {gitdir}] checks for the gitdir file in \n> the current working directory. Since the function has not yet \n> switched to $_gitdir when this check runs, it is almost impossible \n> to find the file. Consequently, this logic never triggers, preventing \n> linked worktrees from being recognized. \n>\n> Maybe the identification of linked worktree should not directly look \n> for the gitdir file, but should check whether there is a.git file and \n> its content points to... /.git/worktrees/... ? Anyways, using the \n> literal {gitdir} to search in the current directory lead to risks. \n>\n> Shroom\n>\nWe cannot get to this code if not inside the gitdir, and if the user set GIT_DIR and/or\nGIT_WORK_TREE to do something clever, that either worked or the code already threw an\nerror. git, without GIT_WORK_TREE set, uses the current directory as the worktree, or the\nparent directory containing .git. So, we must be inside the gitdir if this code path gets hit.\n\nMark\n\nMark\n"},{"id":"543899","messageId":"e5a9a410-bf62-4afd-9560-2dea01fe936b@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-2-mlevedahl@gmail.com","subject":"Re: [PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-22T08:06:06Z","receivedAt":"2026-05-22T08:06:11Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui unconditionally exports _gitdir as GIT_DIR, and _gitworktree as\n> GIT_WORK_TREE, to the environment, and furthermore unconditionally\n> unsets these environment variables in many\n\n\"many cases\"?\n\n> \n> git gui must have a repository, so _gitdir can never be empty and its\n> export is always valid if repository discovery completes successfully.\n\n_gitdir cannot be empty, so we should be able to drop the conditionals\naround 'set env(GIT_DIR) $_gitdir'.\n\n> \n> git gui might not find a worktree, so _gitworktree can be empty. While\n> having no worktree is valid for blame/browser subcommands, exporting\n> GIT_WORK_TREE=<empty> is not valid. Rather, an empty GIT_WORK_TREE\n> raises errors in git builtins, for instance 'git branch --show-current'\n> as used by git, and causes breakage. This is one cause of git blame /\n> git browser not working without a worktree.\n> \n> A user may set GIT_DIR and/or GIT_WORK_TREE to override git's normal\n> discovery rules, including repository configuration of core.worktree\n> and/or worktree specific gitdirs. It is always safe to export the\n> absolute pathnames of the discovered values, even though they may not be\n> needed. However, the gitdir might not be found from the worktree without\n> GIT_DIR being set. Furthermore, the worktree defined by the discovered\n> gitdir might be overridden by GIT_WORK_TREE set before git-gui started.\n> So, it is also sometimes necessary that one or both of these variables\n> is set.\n\nWhile all you say here is true, the actual reason for the dance is more\nlike the simpler: provide a clean slate for the new process and return\nto the old state after it has been started.\n\n> \n> So, let's provide two procs, one to unset GIT_DIR / GIT_WORK_TREE if\n> they are set, one to set GIT_DIR and, if not empty, GIT_WORK_TREE,  so\n> all call sites do the same thing, and problems with _gitworktree == {}\n> are avoided.\n\nThat being said, I propose the two patches below (pasted here for\nreview), after which we do not need these functions anymore IMHO\nbecause the call sites are one-liners around GIT_DIR anyway.\n\nThe commits are available here:\n\ngit fetch https://github.com/j6t/git-gui.git js/unset-git-work-tree\nhttps://github.com/j6t/git-gui/tree/js/unset-git-work-tree\n\n------ 8< ------\nSubject: [PATCH 1/2] git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk\n\nIn the procedure that invokes Gitk, we have a 'cd $_gitworktree'. Such\na change of the current directory is not necessary, because\n\n- if we have a working tree, then the startup routine has already\n  changed the current directory to the root of the working tree, which\n  *is* $_gitworktree; or\n\n- if we are in a bare repository, then there is no point in changing\n  the current directory anywhere. (And $_gitworktree is empty.)\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n git-gui.sh | 6 +-----\n 1 file changed, 1 insertion(+), 5 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 23fe76e498bd..8d2b02b13fa0 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2021,11 +2021,7 @@ proc do_gitk {revs {is_submodule false}} {\n \n \t\tset pwd [pwd]\n \n-\t\tif {!$is_submodule} {\n-\t\t\tif {![is_bare]} {\n-\t\t\t\tcd $_gitworktree\n-\t\t\t}\n-\t\t} else {\n+\t\tif {$is_submodule} {\n \t\t\tcd $current_diff_path\n \t\t\tif {$revs eq {--}} {\n \t\t\t\tset s $file_states($current_diff_path)\n-- \n2.54.0.215.g4fe990ec16\n\n------ 8< ------\nSubject: [PATCH 2/2] git-gui: operate git commands without GIT_WORK_TREE\n\nThe manual page of the git command states about the --git-dir option:\n\n   Specifying the location of the \".git\" directory using this option\n   (or GIT_DIR environment variable) turns off the repository\n   discovery [...], and tells Git that you are at the top level of\n   the working tree.\n\nUse this to our advantage:\n\n- Set GIT_DIR in the environment to the value that was discovered, so\n  that the invoked git commands operate on the same repository\n  database that Git GUI uses even after it changes the working\n  directory.\n\n- After changing the working directory to the top level of the working\n  tree, ensure that GIT_WORK_TREE is not set, because, as per\n  documentation, all git invocations from then on will assume that the\n  current working directory is also the top level working tree.\n\n- Remove the now obsolete GIT_WORK_TREE dance when subordinate Gitk or\n  Git GUI are invoked for a submodule.\n\nDo keep the state of GIT_WORK_TREE if we are in a bare repository,\nbecause Git GUI is not interested in the worktree at all, as no commit\nmode is possible in a bare repository.\n\nThis avoids cases where an empty GIT_WORK_TREE was exported into the\nenvironment, most notably by a call of `git gui blame HEAD file` in a\nbare repository. (Although, this particular error is currently masked\nby an earlier failure in `rev-parse --show-toplevel`, which requires a\nworking tree.)\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n git-gui.sh | 17 +++++++----------\n 1 file changed, 7 insertions(+), 10 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 8d2b02b13fa0..3819f8be2211 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1183,6 +1183,7 @@ if {$_prefix ne {}} {\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n+\tcatch {unset env(GIT_WORK_TREE)}\n \tunset cdup\n } elseif {![is_enabled bare]} {\n \tif {[is_bare]} {\n@@ -1199,6 +1200,7 @@ if {$_prefix ne {}} {\n \t\texit 1\n \t}\n \tset _gitworktree [pwd]\n+\tcatch {unset env(GIT_WORK_TREE)}\n }\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n@@ -1208,7 +1210,6 @@ if {[lindex $_reponame end] eq {.git}} {\n }\n \n set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n \n ######################################################################\n ##\n@@ -2007,7 +2008,7 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitdir _gitworktree\n+\tglobal _gitdir\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n@@ -2041,18 +2042,16 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t\t}\n \t\t\t\tset revs $old_sha1...$new_sha1\n \t\t\t}\n-\t\t\t# GIT_DIR and GIT_WORK_TREE for the submodule are not the ones\n-\t\t\t# we've been using for the main repository, so unset them.\n+\t\t\t# GIT_DIR for the submodule is not the one we've been using for\n+\t\t\t# the main repository, so unset it. (GIT_WORK_TREE is already unset.)\n \t\t\t# TODO we could make life easier (start up faster?) for gitk\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n \t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2076,12 +2075,11 @@ proc do_git_gui {} {\n \t\terror_popup [mc \"Couldn't find git gui in PATH\"]\n \t} else {\n \t\tglobal env\n-\t\tglobal _gitdir _gitworktree\n+\t\tglobal _gitdir\n \n-\t\t# see note in do_gitk about unsetting these vars when\n+\t\t# see note in do_gitk about unsetting this variable when\n \t\t# running tools in a submodule\n \t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n@@ -2089,7 +2087,6 @@ proc do_git_gui {} {\n \t\tsafe_exec_bg [concat $exe gui]\n \n \t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.54.0.215.g4fe990ec16\n\n"},{"id":"543900","messageId":"fdf7aa5c-51ba-4e21-8e4a-5c1fdd8336ab@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-3-mlevedahl@gmail.com","subject":"Re: [PATCH v2 02/11] git-gui: return status from choose_repository::pick","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-22T08:18:55Z","receivedAt":"2026-05-22T08:18:59Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> The repository picker (choose_repository::pick) on success always\n> returns with the current directory at the root of the selected worktree,\n> and with the global variable _gitdir holding the name of the git\n> repository, possibly as a relative path. On failure, _gitdir = {}. If\n> the selection was from the \"recent\" list, no validation has occurred.\n> \n> There are too many side effects in this interface. Note that the picker\n> only supports worktrees with a .git entry in the worktree root, so git\n> repository and worktree discovery will work starting in the current\n> directory on return. So, let's change pick to return a 0/1 value, 1\n> meaning a worktreee + repo was selected and the current directory is the\n> worktree root, and leave validation and setting of _gitdir,\n> _gitworktree, and _prefix to the caller.\n\nWhile the removal of side-effects from the picker is very much desired,\nthe new return value sounds over-engineered at this point, in particular\ndue to this note:\n\n> Note: pick actually does not\n> return if something was not selected, rather it terminates git-gui.\n> But, let's pretend at the call site that pick returns 0/false instead.\n\nIf we need the return value later, let's postpone that part of this\ncommit until then.\n\n> diff --git a/git-gui.sh b/git-gui.sh\n> index 4ba25da7b6..4a736190a9 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1151,10 +1151,16 @@ if {[catch {\n>  \t} err]} {\n>  \tload_config 1\n>  \tapply_config\n> -\tchoose_repository::pick\n> -\tif {![file isdirectory $_gitdir]} {\n> +\tif {![choose_repository::pick]} {\n>  \t\texit 1\n>  \t}\n> +\tif {[catch {\n> +\t\tset _gitdir [git rev-parse --git-dir]\n> +\t} err]} {\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n\nThere's something wrong with the quotes here, and an 'exit 1' is missing.\n\n> +\t}\n> +\tset _prefix {}\n>  \tset picked 1\n>  }\n-- Hannes\n\n"},{"id":"543901","messageId":"b3f8bdf0-fed9-4f02-a86f-ca4d400be852@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-4-mlevedahl@gmail.com","subject":"Re: [PATCH v2 03/11] git-gui: use --absolute-git-dir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-22T08:25:28Z","receivedAt":"2026-05-22T08:25:32Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui uses git rev-parse --git-dir to get the pathname of the\n> discovered git repository. The returned value can be relative, and is\n> '.' if the current directory is the top of the repository directory\n> itself.  git-gui has code to change '.' to [pwd] in this case so that\n> subsequent logic runs.\n> \n> But, git rev-parse supports --absolute-git-dir from fac60b8925\n> (\"rev-parse: add option for absolute or relative path formatting\",\n> 2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\n> this more useful form is always available. Use --absolute-git-dir to\n> always get an absolute path, avoiding the need for other checks, and\n> delete the now unneeded code to fix a relative _gitdir.\n\nVery good!\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 11 ++---------\n>  1 file changed, 2 insertions(+), 9 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 4a736190a9..233c975786 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1146,7 +1146,7 @@ if {[catch {\n>  \t&& [catch {\n>  \t\t# beware that from the .git dir this sets _gitdir to .\n>  \t\t# and _prefix to the empty string\n\nNote that the comment above needs some adjustment as well.\n\n> -\t\tset _gitdir [git rev-parse --git-dir]\n> +\t\tset _gitdir [git rev-parse --absolute-git-dir]\n>  \t\tset _prefix [git rev-parse --show-prefix]\n>  \t} err]} {\n>  \tload_config 1\n> @@ -1155,7 +1155,7 @@ if {[catch {\n>  \t\texit 1\n>  \t}\n>  \tif {[catch {\n> -\t\tset _gitdir [git rev-parse --git-dir]\n> +\t\tset _gitdir [git rev-parse --absolute-git-dir]\n>  \t} err]} {\n>  \t\tcatch {wm withdraw .}\n>  \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n> @@ -1175,13 +1175,6 @@ if {$hashalgorithm eq \"sha1\"} {\n>  \texit 1\n>  }\n>  \n> -# we expand the _gitdir when it's just a single dot (i.e. when we're being\n> -# run from the .git dir itself) lest the routines to find the worktree\n> -# get confused\n> -if {$_gitdir eq \".\"} {\n> -\tset _gitdir [pwd]\n> -}\n> -\n>  if {![file isdirectory $_gitdir]} {\n>  \tcatch {wm withdraw .}\n>  \terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n\n-- Hannes\n\n"},{"id":"543902","messageId":"8d1488ec-c4de-4ddd-b3cd-e1e8b4a343bf@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-5-mlevedahl@gmail.com","subject":"Re: [PATCH v2 04/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-22T08:46:49Z","receivedAt":"2026-05-22T08:46:58Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui attempts to use env(GIT_DIR) directly as the git repository,\n> accepting GIT_DIR if it is a directory. Only if that fails is git\n> rev-parse used to discover the repository.  But, this avoids all of\n> git-core's validity checking on a repository, thus possibly deferring an\n> error to a later step, possibly unexpected. Repository validation should\n> be part of initial setup so that later processing does not need error\n> trapping for configuration errors.\n> \n> Let's just invoke rev-parse so all error checking is done.\n> \n> While here, let's cleanup the error handling.\n> \n> Stop if an error occurs and the user set GIT_DIR or GIT_WORK_TREE.\n> Use of either or both of those variables is supported by git, but their\n> use also means the user has taken responsibility that they are correct,\n> so a failure is something the user must address.\n\nVery much so!\n\n> \n> Otherwise on error, continue the existing behavior and show the\n> repository picker. But, let's move the possible invocation of\n> repository_chooser::pick to a separate code block. This permits adding\n> separate conditions on using pick indepent of repository discovery, and\n> will be exploited later in the series.  Note that the picker always\n> returns with the current directory in the root of a worktree with the\n> git repository is in the .git subdirectory.  The variable \"picked\" is\n> used by git-gui to automatically execute the \"Explore Working Copy\" menu\n> item after the repository picker is run.  This is controlled by config\n> variable gui.autoexplore, and happens after all discovery is complete.\n> \n> Remove a later check on whether _gitdir is a directory: that code\n> cannot be reached without rev-parse already validating the repository.\n> \n> _prefix should not be set before worktree discovery: the prefix is only\n> known after the worktree is found, and at this point we have only\n> discovered the repository.\n\nSorry, but I cannot agree with \"prefix is only known after the worktree\nis found\". The prefix is a property that can be known even if we haven't\nasked where the top-level of the working tree is. See more below.\n\n> This is true even when running the repository\n> picker: that option provides a list of prior selections, and does no\n> validation on the list beyond checking that the directories exist.  For\n> now, just initialize _prefix along with other global variables.\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 48 +++++++++++++++++++++++++++++++++---------------\n>  1 file changed, 33 insertions(+), 15 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 233c975786..c61a6cbd8f 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -374,6 +374,7 @@ set _gitdir {}\n>  set _gitworktree {}\n>  set _isbare {}\n>  set _githtmldir {}\n> +set _prefix {}\n>  set _reponame {}\n>  set _shellpath {@@SHELL_PATH@@}\n>  \n> @@ -1122,6 +1123,24 @@ unset argv0dir\n>  ##\n>  ## repository setup\n>  \n> +proc is_gitvars_error {err} {\n> +\tset havevars 0\n> +\tset GIT_DIR {}\n> +\tset GIT_WORK_TREE {}\n> +\tcatch {set GIT_DIR $::env(GIT_DIR); set havevars 1}\n> +\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE) ; set havevars 1}\n> +\n> +\tif {$havevars} {\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [strcat [mc \"Invalid configuration:\"] \\\n> +\t\t   \"\\n\" \"GIT_DIR: \" $GIT_DIR \\\n> +\t\t   \"\\n\" \"GIT_WORK_TREE: \" $GIT_WORK_TREE \\\n> +\t\t\t\"\\n\\n$err\"]\n> +\t\treturn 1\n> +\t}\n> +\treturn 0\n> +}\n> +\n>  proc set_gitdir_vars {} {\n>  \tglobal _gitdir _gitworktree env\n>  \tif {$_gitdir ne {}} {\n> @@ -1138,17 +1157,22 @@ proc unset_gitdir_vars {} {\n>  \tcatch {unset env(GIT_WORK_TREE)}\n>  }\n>  \n> -set picked 0\n> -if {[catch {\n> -\t\tset _gitdir $env(GIT_DIR)\n> -\t\tset _prefix {}\n> -\t\t}]\n> -\t&& [catch {\n> -\t\t# beware that from the .git dir this sets _gitdir to .\n> -\t\t# and _prefix to the empty string\n> +# find repository.\n> +set _gitdir {}\n> +if {$_gitdir eq {}} {\n> +\tif {[catch {\n>  \t\tset _gitdir [git rev-parse --absolute-git-dir]\n> -\t\tset _prefix [git rev-parse --show-prefix]\n\nYou cannot leave the _prefix empty, because it breaks `git gui browser\nmaster dir` when invoked from a subdirectory of the working tree.\n\nThis line must remain. I see that you add it back in later patch. There\nmay be some motivation to move prefix discovery, but there is no\nmotivation to remove it at this point.\n\n>  \t} err]} {\n> +\t\tif {[is_gitvars_error $err]} {\n> +\t\t\texit 1\n> +\t\t}\n> +\t\tset _gitdir {}\n\nBTW, this line would only be needed if the 'set _prefix' line above stays.\n\n> +\t}\n> +}\n> +\n> +set picked 0\n> +if {$_gitdir eq {}} {\n> +\tunset_gitdir_vars\n>  \tload_config 1\n>  \tapply_config\n>  \tif {![choose_repository::pick]} {\n> @@ -1160,7 +1184,6 @@ if {[catch {\n>  \t\tcatch {wm withdraw .}\n>  \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n>  \t}\n> -\tset _prefix {}\n>  \tset picked 1\n>  }\n>  \n> @@ -1175,11 +1198,6 @@ if {$hashalgorithm eq \"sha1\"} {\n>  \texit 1\n>  }\n>  \n> -if {![file isdirectory $_gitdir]} {\n> -\tcatch {wm withdraw .}\n> -\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n> -\texit 1\n> -}\n>  # _gitdir exists, so try loading the config\n>  load_config 0\n>  apply_config\n\n-- Hannes\n\n"},{"id":"543916","messageId":"eb748327-6652-4477-82bb-9db9f8388ec0@gmail.com","threadId":"65531","inReplyTo":"e5a9a410-bf62-4afd-9560-2dea01fe936b@kdbg.org","subject":"Re: [PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-22T11:54:28Z","receivedAt":"2026-05-22T11:54:30Z","isPatch":true,"body":"\n\nOn 5/22/26 4:06 AM, Johannes Sixt wrote:\n> Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n>> 2.54.0.215.g4fe990ec16\n>>\n>> ------ 8< ------\n>> Subject: [PATCH 2/2] git-gui: operate git commands without GIT_WORK_TREE\n>>\n>> The manual page of the git command states about the --git-dir option:\n>>\n>>    Specifying the location of the \".git\" directory using this option\n>>    (or GIT_DIR environment variable) turns off the repository\n>>    discovery [...], and tells Git that you are at the top level of\n>>    the working tree.\n>>\n>> Use this to our advantage:\n>>\n>> - Set GIT_DIR in the environment to the value that was discovered, so\n>>   that the invoked git commands operate on the same repository\n>>   database that Git GUI uses even after it changes the working\n>>   directory.\n>>\n>> - After changing the working directory to the top level of the working\n>>   tree, ensure that GIT_WORK_TREE is not set, because, as per\n>>   documentation, all git invocations from then on will assume that the\n>>   current working directory is also the top level working tree.\n>>\n>> - Remove the now obsolete GIT_WORK_TREE dance when subordinate Gitk or\n>>   Git GUI are invoked for a submodule.\n>\n> 2.54.0.215.g4fe990ec16\n>\n> ------ 8< ------\n> Subject: [PATCH 2/2] git-gui: operate git commands without GIT_WORK_TREE\n>\n> The manual page of the git command states about the --git-dir option:\n>\n>    Specifying the location of the \".git\" directory using this option\n>    (or GIT_DIR environment variable) turns off the repository\n>    discovery [...], and tells Git that you are at the top level of\n>    the working tree.\n>\n> Use this to our advantage:\n>\n> - Set GIT_DIR in the environment to the value that was discovered, so\n>   that the invoked git commands operate on the same repository\n>   database that Git GUI uses even after it changes the working\n>   directory.\n>\n> - After changing the working directory to the top level of the working\n>   tree, ensure that GIT_WORK_TREE is not set, because, as per\n>   documentation, all git invocations from then on will assume that the\n>   current working directory is also the top level working tree.\n>\n> - Remove the now obsolete GIT_WORK_TREE dance when subordinate Gitk or\n>   Git GUI are invoked for a submodule.\n> 2.54.0.215.g4fe990ec16\n>\n> ------ 8< ------\n> Subject: [PATCH 2/2] git-gui: operate git commands without GIT_WORK_TREE\n>\n> The manual page of the git command states about the --git-dir option:\n>\n>    Specifying the location of the \".git\" directory using this option\n>    (or GIT_DIR environment variable) turns off the repository\n>    discovery [...], and tells Git that you are at the top level of\n>    the working tree.\n>\n> Use this to our advantage:\n>\n> - Set GIT_DIR in the environment to the value that was discovered, so\n>   that the invoked git commands operate on the same repository\n>   database that Git GUI uses even after it changes the working\n>   directory.\n>\n> - After changing the working directory to the top level of the working\n>   tree, ensure that GIT_WORK_TREE is not set, because, as per\n>   documentation, all git invocations from then on will assume that the\n>   current working directory is also the top level working tree.\n>\n> - Remove the now obsolete GIT_WORK_TREE dance when subordinate Gitk or\n>   Git GUI are invoked for a submodule.\n\nThe manual page is incomplete: if the repository has set core.worktree=/somehere, that is\nthe root of the worktree and the current directory is always ignored. git rev-parse will\nreport /somewhere as the answer to --show-toplevel regardless of current directory, even\nif inside the gitdir, and even if GIT_DIR is used.\n\nThe user can override with GIT_WORK_TREE, and if so we must keep GIT_WORK_TREE in the\nenvironment if it was set. So, an alternative for deciding when to export is\n\nif core.worktree and GIT_WORK_TREE are both set AND GIT_WORK_TREE != core.worktree\n    export GIT_WORK_TREE\n\nIf the user set GIT_WORK_TREE empty, git rev-parse will throw an error so we would never\nto far enough to try to export that.\n\nMark\n\n\n"},{"id":"543918","messageId":"f54ea491-4165-43fa-b1ac-14d0fe63fc75@gmail.com","threadId":"65531","inReplyTo":"8d1488ec-c4de-4ddd-b3cd-e1e8b4a343bf@kdbg.org","subject":"Re: [PATCH v2 04/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-22T12:04:49Z","receivedAt":"2026-05-22T12:04:51Z","isPatch":true,"body":"\n\nOn 5/22/26 4:46 AM, Johannes Sixt wrote:\n> Sorry, but I cannot agree with \"prefix is only known after the worktree\n> is found\". The prefix is a property that can be known even if we haven't\n> asked where the top-level of the working tree is. See more below.\nRewording is necessary, but:\nThis patch already adds\nset _prefix {}\nto the globals init block.\n\n\n_prefix can only be non-empty if normal gitdir / gitworktree discovery (without GIT_DIR)\nruns and finds the current directory is a descendent of the worktree root. That step comes\nlater, even if the picker is run.\n\nMark\n"},{"id":"543920","messageId":"tencent_1DC2E8E76788506425483808F7CD56931406@qq.com","threadId":"65531","inReplyTo":"bb38c5d4-b388-4eca-badd-69ec7ce67b90@gmail.com","subject":"Re: [PATCH v2 10/11] git-gui: adapt blame/browser parsing for bare operation","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-22T15:05:25Z","receivedAt":"2026-05-22T15:06:15Z","isPatch":true,"body":"On 5/22/26 1:35 AM, Mark Levedahl wrote: \n> Asking the worktree that is on version 33 about whether frotz is a directory in version 2\n> is just asking for trouble, at best the worktree is authoritative for the checked out\n> version, but even then there can be uncommitted changed. In the root of git-gui, I get\n> \n> /git-gui.sh browser gitgui-0.9.0 Makefile\n> 'Makefile' is not a directory in rev 'gitgui-0.9.0'\n> \n> so the types of objects are being checked.\n> \n> Mark\n\nYou are right. The input is subdir instead of subdir/. All the \ncurrent tests work well. Patch 10/11 does not cause damage to the \nfunctionality of browser and blame commands. \n\nShroom\n\n"},{"id":"543921","messageId":"tencent_9FA5CF10A1D7CAC770366E84AD5CA9A2D107@qq.com","threadId":"65531","inReplyTo":"780a52d9-7119-4870-990a-16280005503d@gmail.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-22T15:09:26Z","receivedAt":"2026-05-22T15:10:14Z","isPatch":true,"body":"On 5/22/26 1:45 AM, Mark Levedahl wrote:\n> >\n> > Additionally, [file exists {gitdir}] checks for the gitdir file in \n> > the current working directory. Since the function has not yet \n> > switched to $_gitdir when this check runs, it is almost impossible \n> > to find the file. Consequently, this logic never triggers, preventing \n> > linked worktrees from being recognized. \n> >\n> > Maybe the identification of linked worktree should not directly look \n> > for the gitdir file, but should check whether there is a.git file and \n> > its content points to... /.git/worktrees/... ? Anyways, using the \n> > literal {gitdir} to search in the current directory lead to risks. \n> >\n> > Shroom\n> >\n> We cannot get to this code if not inside the gitdir, and if the user set GIT_DIR and/or\n> GIT_WORK_TREE to do something clever, that either worked or the code already threw an\n> error. git, without GIT_WORK_TREE set, uses the current directory as the worktree, or the\n> parent directory containing .git. So, we must be inside the gitdir if this code path gets hit.\n> \n> Mark\n\nThe relative path issue with {gitdir} is indeed difficult to trigger \nin practice. To trigger this problem, the following conditions must \nbe met simultaneously: \n- git rev-parse --absolute-git-dir succeeds (the repository is valid). \n- git rev-parse --show-toplevel fails (the working tree is \nundetectable).\n- The current directory is not $_gitdir.\n- A gitdir file exists under $_gitdir (i.e., the gitdir of a linked \nworktree). \n\nUnluckily, this occurs in:\n\n```MINGW64 Shell\n# Firstly, enter a test folder\nmkdir test-main\ncd test-main\n\ngit init\n\necho main > file.txt\ngit add file.txt\ngit commit -m \"initial\"\n\n# In feature branch\ngit branch feature\n\n# Add worktree\ngit worktree add ../test-feature feature\n\ncd ../\ncd test-feature\ncat .git\n\ncd ../\n\nmkdir outside\ncd outside\n\nexport GIT_DIR=\"/(PREVIOUSPATH)/test-main/.git/worktrees/feature\"\nunset GIT_WORK_TREE\ngit gui blame HEAD file.txt --trace\n```\n\n```Wish\nputs $_gitworktree\n```\n\nThe current directory is \"outside\", not the expected linked worktree. \n[file exists {gitdir}] in find_worktree_from_gitdir searched in the \ncurrent directory but failed, resulting in the inability to identify \nlinked worktree. The blame mode can still work without worktree. \n\nShroom\n\n"},{"id":"543932","messageId":"8d03455a-82dc-4d32-aa57-af3d92714e31@gmail.com","threadId":"65531","inReplyTo":"tencent_9FA5CF10A1D7CAC770366E84AD5CA9A2D107@qq.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-22T16:57:37Z","receivedAt":"2026-05-22T16:57:39Z","isPatch":true,"body":"\n\nOn 5/22/26 11:09 AM, Shroom Moo wrote:\n> On 5/22/26 1:45 AM, Mark Levedahl wrote:\n>>> Additionally, [file exists {gitdir}] checks for the gitdir file in \n>>> the current working directory. Since the function has not yet \n>>> switched to $_gitdir when this check runs, it is almost impossible \n>>> to find the file. Consequently, this logic never triggers, preventing \n>>> linked worktrees from being recognized. \n>>>\n>>> Maybe the identification of linked worktree should not directly look \n>>> for the gitdir file, but should check whether there is a.git file and \n>>> its content points to... /.git/worktrees/... ? Anyways, using the \n>>> literal {gitdir} to search in the current directory lead to risks. \n>>>\n>>> Shroom\n>>>\n>> We cannot get to this code if not inside the gitdir, and if the user set GIT_DIR and/or\n>> GIT_WORK_TREE to do something clever, that either worked or the code already threw an\n>> error. git, without GIT_WORK_TREE set, uses the current directory as the worktree, or the\n>> parent directory containing .git. So, we must be inside the gitdir if this code path gets hit.\n>>\n>> Mark\n> The relative path issue with {gitdir} is indeed difficult to trigger \n> in practice. To trigger this problem, the following conditions must \n> be met simultaneously: \n> - git rev-parse --absolute-git-dir succeeds (the repository is valid). \n> - git rev-parse --show-toplevel fails (the working tree is \n> undetectable).\n> - The current directory is not $_gitdir.\n> - A gitdir file exists under $_gitdir (i.e., the gitdir of a linked \n> worktree). \n>\n> Unluckily, this occurs in:\n>\n> ```MINGW64 Shell\n> # Firstly, enter a test folder\n> mkdir test-main\n> cd test-main\n>\n> git init\n>\n> echo main > file.txt\n> git add file.txt\n> git commit -m \"initial\"\n>\n> # In feature branch\n> git branch feature\n>\n> # Add worktree\n> git worktree add ../test-feature feature\n>\n> cd ../\n> cd test-feature\n> cat .git\n>\n> cd ../\n>\n> mkdir outside\n> cd outside\n>\n> export GIT_DIR=\"/(PREVIOUSPATH)/test-main/.git/worktrees/feature\"\n> unset GIT_WORK_TREE\n> git gui blame HEAD file.txt --trace\n> ```\n>\n> ```Wish\n> puts $_gitworktree\n> ```\n>\n> The current directory is \"outside\", not the expected linked worktree. \n> [file exists {gitdir}] in find_worktree_from_gitdir searched in the \n> current directory but failed, resulting in the inability to identify \n> linked worktree. The blame mode can still work without worktree. \n>\n> Shroom\n>\nYou exported GIT_DIR pointing to a valid gitdir, and as documented git takes your current\ndirectory (\"outside\") to be the worktree. This is working as expected. Had finding a\nworktree failed, git-gui would issue an error message and stop because you defined GIT_DIR.\n\nMark\n"},{"id":"543956","messageId":"fca71977-cc14-4b38-b310-9808b61880f5@gmail.com","threadId":"65531","inReplyTo":"8d1488ec-c4de-4ddd-b3cd-e1e8b4a343bf@kdbg.org","subject":"Re: [PATCH v2 04/11] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-22T23:00:27Z","receivedAt":"2026-05-22T23:00:31Z","isPatch":true,"body":"On 5/22/26 4:46 AM, Johannes Sixt wrote:\n> Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n\n> \n> Sorry, but I cannot agree with \"prefix is only known after the worktree\n> is found\". The prefix is a property that can be known even if we haven't\n> asked where the top-level of the working tree is. See more below.\n> \n>> This is true even when running the repository\n>> picker: that option provides a list of prior selections, and does no\n>> validation on the list beyond checking that the directories exist.  For\n>> now, just initialize _prefix along with other global variables.\n>>\n\n> \n> You cannot leave the _prefix empty, because it breaks `git gui browser\n> master dir` when invoked from a subdirectory of the working tree.\n> \n> This line must remain. I see that you add it back in later patch. There\n> may be some motivation to move prefix discovery, but there is no\n> motivation to remove it at this point.\n\n\n\nNever mind, I confused myself on the process. THis patch has only affected repo discovery,\nworktree discovery is later so should be left untouched here.\n\nMark\n"},{"id":"543959","messageId":"c8d1ab1e-e0cb-44e2-afcd-728b7b43774c@kdbg.org","threadId":"65531","inReplyTo":"tencent_E13EB585242AD7C263B8B3B732A428465D09@qq.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T08:01:40Z","receivedAt":"2026-05-23T08:01:49Z","isPatch":true,"body":"Am 21.05.26 um 06:55 schrieb Shroom Moo:\n> On 5/21/26 4:24 AM, Mark Levedahl wrote:\n>> +\t} elseif [file exists {gitdir}] {\n>> +\t\tif {[catch {\n>> +\t\t\tset fd_gitdir [open {gitdir} {r}]\n>> +\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n>> +\t\t\tcatch {close $fd_gitdir}\n>> +\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n>> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n>> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n>> +\t\t\t\tset worktree {}\n>> +\t\t\t}\n>> +\t\t}]} {\n>> +\t\t\tcatch {close $fd_gitdir}\n>> +\t\t\tset worktree {}\n>> +\t\t}\n>> +\t}\n\n> Additionally, [file exists {gitdir}] checks for the gitdir file in \n> the current working directory. Since the function has not yet \n> switched to $_gitdir when this check runs, it is almost impossible \n> to find the file. Consequently, this logic never triggers, preventing \n> linked worktrees from being recognized. \n\nI think you are misunderstanding which use-case this code is addressing.\nThe case can be triggered very easily.\n\nFirst, the code before the part we see above is intended for the special\ncase where we start in a .git, where `--show-toplevel` bails out and we\ndefine the worktree to be the directory containing .git.\n\nHowever, if we start in .git/worktrees/feature, then the code cited\nabove kicks in, because `--show-toplevel` still bails out,\n`--absolute-git-dir` does not end in '.git', but now we have a file\nnamed 'gitdir' in the current directory. In this case, we define (and\nthis is new with this patch) that the worktree is the one where the\n'gitdir' points.\n\n-- Hannes\n\n"},{"id":"543960","messageId":"b332c7d9-c86b-4d4b-a873-1600d910a237@kdbg.org","threadId":"65531","inReplyTo":"eb748327-6652-4477-82bb-9db9f8388ec0@gmail.com","subject":"Re: [PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T08:18:59Z","receivedAt":"2026-05-23T08:19:10Z","isPatch":true,"body":"Am 22.05.26 um 13:54 schrieb Mark Levedahl:\n> The manual page is incomplete: if the repository has set core.worktree=/somehere, that is\n> the root of the worktree and the current directory is always ignored. git rev-parse will\n> report /somewhere as the answer to --show-toplevel regardless of current directory, even\n> if inside the gitdir, and even if GIT_DIR is used.\n> \n> The user can override with GIT_WORK_TREE, and if so we must keep GIT_WORK_TREE in the\n> environment if it was set. [...]\n\nOh, well, these intricacies! Let's scrap my patch and keep yours.\n\nThe other patch that removes cd $_gitworktree from do_gitk should still\nbe good, I think.\n\n-- Hannes\n\n"},{"id":"543972","messageId":"20260523114629.1298-1-bootaina702@gmail.com","threadId":"65531","inReplyTo":"b332c7d9-c86b-4d4b-a873-1600d910a237@kdbg.org","subject":"Re: [PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Aina Boot","fromEmail":"bootaina702@gmail.com","sentAt":"2026-05-23T11:46:26Z","receivedAt":"2026-05-23T11:47:25Z","isPatch":true,"body":"On 5/23/26 8:19 AM, Johannes Sixt wrote:\n> The other patch that removes cd $_gitworktree from do_gitk should still\n> be good, I think.\n\nAgree. It either succeededly set the directory or work without a \nworktree, cd is practically unnecessary. \n\nAina\n"},{"id":"543974","messageId":"tencent_6D48318C367E3E0A7D54C684DB701F6F590A@qq.com","threadId":"65531","inReplyTo":"c8d1ab1e-e0cb-44e2-afcd-728b7b43774c@kdbg.org","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Shroom Moo","fromEmail":"egg_mushroomcow@foxmail.com","sentAt":"2026-05-23T11:47:44Z","receivedAt":"2026-05-23T11:49:59Z","isPatch":true,"body":"On 5/23/26 4:01 PM, Johannes Sixt wrote:\n> Am 21.05.26 um 06:55 schrieb Shroom Moo:\n>> On 5/21/26 4:24 AM, Mark Levedahl wrote:\n>>> +\t} elseif [file exists {gitdir}] {\n>>> +\t\tif {[catch {\n>>> +\t\t\tset fd_gitdir [open {gitdir} {r}]\n>>> +\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n>>> +\t\t\tcatch {close $fd_gitdir}\n>>> +\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n>>> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n>>> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n>>> +\t\t\t\tset worktree {}\n>>> +\t\t\t}\n>>> +\t\t}]} {\n>>> +\t\t\tcatch {close $fd_gitdir}\n>>> +\t\t\tset worktree {}\n>>> +\t\t}\n>>> +\t}\n\n>> Additionally, [file exists {gitdir}] checks for the gitdir file in \n>> the current working directory. Since the function has not yet \n>> switched to $_gitdir when this check runs, it is almost impossible \n>> to find the file. Consequently, this logic never triggers, preventing \n>> linked worktrees from being recognized. \n\n> I think you are misunderstanding which use-case this code is addressing.\n> The case can be triggered very easily.\n\n> First, the code before the part we see above is intended for the special\n> case where we start in a .git, where `--show-toplevel` bails out and we\n> define the worktree to be the directory containing .git.\n> However, if we start in .git/worktrees/feature, then the code cited\n> above kicks in, because `--show-toplevel` still bails out,\n> `--absolute-git-dir` does not end in '.git', but now we have a file\n> named 'gitdir' in the current directory. In this case, we define (and\n> this is new with this patch) that the worktree is the one where the\n> 'gitdir' points.\n> \n> -- Hannes\n\nI see. The condition is unrelated to this patch. Users should handle \nthis case as assigning manually by rule. Indeed we don't need to \nmodify it. \n\nShroom\n\n"},{"id":"543975","messageId":"40533d91-660d-4dad-b8fd-a11dac9a8b0a@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-7-mlevedahl@gmail.com","subject":"Re: [PATCH v2 06/11] git-gui: use git rev-parse for worktree discovery","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T13:26:56Z","receivedAt":"2026-05-23T13:27:09Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git gui uses a combination of tcl code and git invocations to determine\n> the worktree and the location with respect to the worktree root\n> (_prefix). But, git rev-parse provides all of this information directly,\n> and assures full error and configuration checking are done by git\n> itself. The entirety of discovery in normal configurations involves\n> \n> \tgit rev-parse --show-toplevel (gets worktree root)\n> \tgit rev-parse --show-prefix (shows location wrt the root)\n> \n> An error thrown on either of these lines means the worktree discovered\n> by git is unusable, or git did not discover a worktree because the\n> current directory is inside the repository. If the user has defined\n> GIT_DIR or GIT_WORK_TREE, this is a user configuration error and git-gui\n> should stop.\n> \n> Otherwise, the blame or browser subcommands can be used without a\n> worktree.\n> \n> A separate error might occur when changing to the root of the discovered\n> worktree. The cause would be file system related and completely outside\n> of git's control. So, the final \"cd $worktree_root\" is separately\n> trapped.\n> \n> Discovery of the repository and the worktree must be guarded to trap\n> errors: the intent is that any configuration problems are caught during\n> discovery, and later processing need not include error trapping and\n> recovery. So, move all worktree discovery code to be immediately after\n> repository discovery.\n> \n> This does move configuration loading to occur after worktree discovery\n> rather than before. None of the code executed in worktree discovery has\n> any option controlled by a git-gui configuration variable, so no impact\n> is expected. git itself will always read the repository configuration,\n> including worktree specific configuration data if that exists, so this\n> is unaffected by when git-gui loads its own config data, and we cannot\n> be sure the full worktree dependent configuration can be loaded before\n> full discovery is complete.\n\nVery good!\n\nWhen you move code around, please do not apply style changes so that\n\ngit show --color-moved --color-moved-ws=allow-indentation-change\n\ncan prove that no change was intended.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 64 +++++++++++++++++++++++++-----------------------------\n>  1 file changed, 30 insertions(+), 34 deletions(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 936c309e59..8fe25fe188 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1164,6 +1164,36 @@ if {$_gitdir eq {}} {\n>  \tset picked 1\n>  }\n>  \n> +# find worktree, continue without if not required\n> +if {[catch {\n> +\tset _gitworktree [git rev-parse --show-toplevel]\n> +\tset _prefix [git rev-parse --show-prefix]\n> +} err]} {\n> +\tif {[is_gitvars_error $err]} {\n> +\t\texit 1\n> +\t}\n> +\tset _gitworktree {}\n> +\tset _prefix {}\n> +}\n> +\n> +if {![is_bare]} {\n> +\tif {[catch {\n> +\t\tcd $_gitworktree\n> +\t} err]} {\n> +\t\tcatch {wm withdraw .}\n> +\t\terror_popup [strcat [mc \"Cannot change to discovered worktree: \"] \\\n> +\t\t\t\"$_gitworktree\" \"\\n\\n$err\"]\n> +\t\texit 1;\n> +\t}\n> +} elseif {![is_enabled bare]} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n> +\texit 1\n> +}\n> +\n> +# repository and worktree config are complete, export them\n> +set_gitdir_vars\n> +\n>  # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n>  set hashalgorithm [git rev-parse --show-object-format]\n>  if {$hashalgorithm eq \"sha1\"} {\n> @@ -1179,37 +1209,6 @@ if {$hashalgorithm eq \"sha1\"} {\n>  load_config 0\n>  apply_config\n>  \n> -set _gitworktree [git rev-parse --show-toplevel]\n> -\n> -if {$_prefix ne {}} {\n> -\tif {$_gitworktree eq {}} {\n> -\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n> -\t} else {\n> -\t\tset cdup $_gitworktree\n> -\t}\n> -\tif {[catch {cd $cdup} err]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n> -\t\texit 1\n> -\t}\n> -\tset _gitworktree [pwd]\n> -\tunset cdup\n> -} elseif {![is_enabled bare]} {\n> -\tif {[is_bare]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n> -\t\texit 1\n> -\t}\n> -\tif {$_gitworktree eq {}} {\n> -\t\tset _gitworktree [file dirname $_gitdir]\n> -\t}\n> -\tif {[catch {cd $_gitworktree} err]} {\n> -\t\tcatch {wm withdraw .}\n> -\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n> -\t\texit 1\n> -\t}\n> -\tset _gitworktree [pwd]\n> -}\n>  set _reponame [file split [file normalize $_gitdir]]\n>  if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end-1]\n> @@ -1217,9 +1216,6 @@ if {[lindex $_reponame end] eq {.git}} {\n>  \tset _reponame [lindex $_reponame end]\n>  }\n>  \n> -# Export the final paths\n> -set_gitdir_vars\n> -\n>  ######################################################################\n>  ##\n>  ## global init\n\n-- Hannes\n\n"},{"id":"543976","messageId":"a1e9da65-f8dd-4544-bbc9-d3b01328cebe@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-8-mlevedahl@gmail.com","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T14:06:00Z","receivedAt":"2026-05-23T14:06:05Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n> 2010-01-23), has had the intent to allow starting from inside a\n> repository, then switching to the parent directory if that is a valid\n> worktree.\n\nI can imagine that this kind of use occurs in \"Git GUI here\" menu item\nof the file explorer on Windows. So, we should resurrect the feature.\n\n> \n> This certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n> --show-toplevel without a worktree an error\", 2019-11-19) in git, but\n> breaking this git-gui feature was unintentional.\n> \n> There are (at least) 3 cases where the gitdir can tell us where the\n> worktree is, and we would like all to work:\n> \n> - core.worktree is set, and points to a valid worktree. This is already\n>   handled  by git rev-parse --show-toplevel, even when not in the worktree.\n>   There is nothing more to do in this case.\n> \n> - the gitdir is embedded in a worktree as subdirectory .git. The parent\n>   is (or at least should be) a valid worktree. This worked long ago.\n> \n> - the gitdir is a worktree specific directory (under\n>   <mainrepo>/worktrees/worktree_name), within which there is a file\n>   \"gitdir\" pointing to .git in the worktree. git gui never learned to\n>   handle this case.\n> \n> Let's handle the latter two cases. Always check that the discovered\n> worktree is valid and points to the already discovered gitdir according\n> to git rev-parse. This avoids issues that may arise because we are\n> discovering from the gitdir up, rather than the worktree down, and file\n> system non-posix behavior or misconfiguration of git might cause\n> confusion.  For instance, a manually moved worktree might not be where\n> the gitdir points.\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 42 ++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 42 insertions(+)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index 8fe25fe188..aeb7ed3548 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -1100,6 +1100,41 @@ unset argv0dir\n>  ##\n>  ## repository setup\n>  \n> +proc find_worktree_from_gitdir {} {\n> +\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree.\n> +\t# Assure parent is a worktree and using the git repository already discovered.\n> +\t# Also, handle case of being in a worktree's gitdir, where file \"gitdir\" points to\n> +\t# gitlink file .git in the real worktree.\n> +\tset worktree {}\n> +\tif {[file tail $::_gitdir] eq {.git}} {\n> +\t\tif {[catch {\n> +\t\t\tset gitdir_parent [file dirname $::_gitdir]\n> +\t\t\tset worktree [git -C $gitdir_parent rev-parse --show-toplevel]\n> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n> +\t\t\t\tset worktree {}\n\nI tried to come up with a situation where we end up here, but couldn't.\nWhen would this happen? If it actually can't happen, I would prefer to\nspawn fewer git processes and just take the result of 'file dirname'.\n\nIf the code must remain, can we please rename one of gitdir_parent or\nparent_gitdir?\n\n> +\t\t\t}\n> +\t\t}]} {\n> +\t\t\tset worktree {}\n> +\t\t}\n> +\t} elseif [file exists {gitdir}] {\n> +\t\tif {[catch {\n> +\t\t\tset fd_gitdir [open {gitdir} {r}]\n> +\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n> +\t\t\tcatch {close $fd_gitdir}\n> +\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n> +\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n\nSince worktrees can be messed up quite easily, it looks reasonable to\ncheck whether the worktree points back to the gitdir. (But I haven't\ntried to construct a case that passes the check in the next line.)\n\n> +\t\t\tif {$::_gitdir ne $parent_gitdir} {\n> +\t\t\t\tset worktree {}\n> +\t\t\t}\n> +\t\t}]} {\n> +\t\t\tcatch {close $fd_gitdir}\n> +\t\t\tset worktree {}\n> +\t\t}\n> +\t}\n> +\treturn $worktree\n> +}\n> +\n>  proc is_gitvars_error {err} {\n>  \tset havevars 0\n>  \tset GIT_DIR {}\n> @@ -1176,6 +1211,13 @@ if {[catch {\n>  \tset _prefix {}\n>  }\n>  \n> +if {[is_bare]} {\n> +\t# Maybe we are in an embedded or worktree specific gitdir\n> +\tif {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {\n> +\t\tset _prefix {}\n> +\t}\n> +}\n> +\n>  if {![is_bare]} {\n>  \tif {[catch {\n>  \t\tcd $_gitworktree\n\n-- Hannes\n\n"},{"id":"543977","messageId":"d677972d-2578-4261-8264-95b48ef65a07@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-9-mlevedahl@gmail.com","subject":"Re: [PATCH v2 08/11] git-gui: use HEAD as current branch when detached (bug fix)","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T14:08:28Z","receivedAt":"2026-05-23T14:08:32Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> commit f87a36b697 (\"git-gui: use git-branch --show-current\", 2024-02-12)\n> changed git-gui to use git-branch to access refs, rather than directly\n> reading files as doing the latter is not compatible with the reftable\n> backend. git branch --show-current reports an empty branch name when the\n> head is detached, and in this case load_current_branch needs to report\n> HEAD using special case logic as it did prior to the above commit. Make\n> it do so.\n> \n> This addresses an issue with git-gui browser failing with a detached\n> head.\n\nNice catch. I'll reorder this as the first commit.\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 3 +++\n>  1 file changed, 3 insertions(+)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index aeb7ed3548..a72d8a59ec 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -648,6 +648,9 @@ proc load_current_branch {} {\n>  \n>  \tset current_branch [git branch --show-current]\n>  \tset is_detached [expr [string length $current_branch] == 0]\n> +\tif {$is_detached} {\n> +\t\tset current_branch {HEAD}\n> +\t}\n>  }\n>  \n>  auto_load tk_optionMenu\n\n-- Hannes\n\n"},{"id":"543978","messageId":"ae3cdc22-2f88-4222-bab7-403408373a53@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-10-mlevedahl@gmail.com","subject":"Re: [PATCH v2 09/11] git-gui: allow specifying path '.' to the browser","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T14:23:27Z","receivedAt":"2026-05-23T14:23:31Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> Invoking \"git-gui browser rev .\" should show the file browser for the\n> commitish rev, starting at the current directory. When the current\n> directory is the working tree root, this errors out in normalize_relpath\n> because the '.' is removed, yielding an empty list as argument to [file\n> join ...]. The browser function demands \"./\" in this case, so make it\n> so. (./ works on Windows as well because g4w accepts posix file\n> naming).\n\nI wonder why we need \"./\" instead of plain \".\". The latter works just\nfine in my tests (on Linux).\n\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n>  git-gui.sh | 6 +++++-\n>  1 file changed, 5 insertions(+), 1 deletion(-)\n> \n> diff --git a/git-gui.sh b/git-gui.sh\n> index a72d8a59ec..d373457901 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -3007,7 +3007,11 @@ proc normalize_relpath {path} {\n>  \t\t}\n>  \t\tlappend elements $item\n>  \t}\n> -\treturn [eval file join $elements]\n> +\tif {$elements ne {}} {\n> +\t\treturn [eval file join $elements]\n> +\t} else {\n> +\t\treturn {./}\n> +\t}\n>  }\n>  \n>  # -- Not a normal commit type invocation?  Do that instead!\n\n-- Hannes\n\n"},{"id":"543980","messageId":"27452460-e709-45c6-b411-10118ab9bf95@gmail.com","threadId":"65531","inReplyTo":"a1e9da65-f8dd-4544-bbc9-d3b01328cebe@kdbg.org","subject":"Re: [PATCH v2 07/11] git-gui: try harder to find worktree from gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-23T15:33:03Z","receivedAt":"2026-05-23T15:33:06Z","isPatch":true,"body":"\n\nOn 5/23/26 10:06 AM, Johannes Sixt wrote:\n> I tried to come up with a situation where we end up here, but couldn't.\n> When would this happen? If it actually can't happen, I would prefer to\n> spawn fewer git processes and just take the result of 'file dirname'.\n>\n> If the code must remain, can we please rename one of gitdir_parent or\n> parent_gitdir?\nThere are some odd cases I've seen, mostly driven by network file systems (using Samba or\nNFS or ...) that don't behave as POSIX. Perhaps that is reasonably out of scope. The more\nI think about this, I'll delete that part.\n\nSymlinks can create odd cases:\n\nConsider /tmp/main  and  /tmp/worktree. The latter has a .git entry that is a symlink to\n/tmp/main.git. git rev-parse --absolute-git-dir shows\n    in dir /tmp/worktree/.git           /tmp/main.git\nSo, git-gui would start in /tmp/main, and not in /tmp/worktree.\n\nIf using git new-workdir (which is still in the wild), this creates a .git dir with\nsymlinks to all the subdirs. Now git rev-parse reports\n   in dir /tmp/worktree.git          /tmp/worktree/.git\n   in dir /tmp/worktree/.git/logs   /tmp/main/.git\nSo, as long as this is started in the top level of .git, it's ok.\n\nWhile the shell understands we descended into a symlink and reports pwd does not\nde-referencing the symlink, tcl always dereferences the symlinks. So, any ability to\ncontain this behavior is very likely system and shell dependent.\n\nSo, in summary, I'll probably simplify the check to just --show-toplevel run in the dir\nabove .git.\n\n\nMark\n\n"},{"id":"543981","messageId":"52331f7c-1c97-4de4-bfb6-05140ffea6b4@gmail.com","threadId":"65531","inReplyTo":"ae3cdc22-2f88-4222-bab7-403408373a53@kdbg.org","subject":"Re: [PATCH v2 09/11] git-gui: allow specifying path '.' to the browser","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-23T15:43:12Z","receivedAt":"2026-05-23T15:43:14Z","isPatch":true,"body":"\n\nOn 5/23/26 10:23 AM, Johannes Sixt wrote:\n> Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n>> Invoking \"git-gui browser rev .\" should show the file browser for the\n>> commitish rev, starting at the current directory. When the current\n>> directory is the working tree root, this errors out in normalize_relpath\n>> because the '.' is removed, yielding an empty list as argument to [file\n>> join ...]. The browser function demands \"./\" in this case, so make it\n>> so. (./ works on Windows as well because g4w accepts posix file\n>> naming).\n> I wonder why we need \"./\" instead of plain \".\". The latter works just\n> fine in my tests (on Linux).\n'.' caused errors in browser::new in for me before while './' worked, but now I find both\nwork. I'm confused, this must have been an interaction with something else in flight at\nthe time, will revert to '.' if that passes my tests on Windows as well as it is more\nconsistent of not adding '/' to a dirname.\n\nMark\n"},{"id":"543982","messageId":"c51e5c66-6148-48c4-9d3b-bb9ed4087115@gmail.com","threadId":"65531","inReplyTo":"b332c7d9-c86b-4d4b-a873-1600d910a237@kdbg.org","subject":"Re: [PATCH v2 01/11] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-23T16:08:17Z","receivedAt":"2026-05-23T16:08:20Z","isPatch":true,"body":"\n\nOn 5/23/26 4:18 AM, Johannes Sixt wrote:\n> Am 22.05.26 um 13:54 schrieb Mark Levedahl:\n>> The manual page is incomplete: if the repository has set core.worktree=/somehere, that is\n>> the root of the worktree and the current directory is always ignored. git rev-parse will\n>> report /somewhere as the answer to --show-toplevel regardless of current directory, even\n>> if inside the gitdir, and even if GIT_DIR is used.\n>>\n>> The user can override with GIT_WORK_TREE, and if so we must keep GIT_WORK_TREE in the\n>> environment if it was set. [...]\n> Oh, well, these intricacies! Let's scrap my patch and keep yours.\n>\n> The other patch that removes cd $_gitworktree from do_gitk should still\n> be good, I think.\n>\n> -- Hannes\n>\nRemoving cd $_gitworktree should be ok, we are already in that directory, or don't have a\nworktree, and I did that once myself before dropping it as I don't really understand\ndo_gitk. It should not change any behavior. So, go ahead and add it wherever you wish.\n\nBut, I don't understand unsetting GIT_DIR and GIT_WORK_TREE for gitk. If we needed them\nfor git in the super module, we need them for submodules as well, but have no idea how to\nadjust them. Simply unsetting them cannot be right. Out of scope for me. But, there are\nsome dragons lurking around this proc.\n\nMark\n"},{"id":"543984","messageId":"20260523181950.201345-1-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-11-mlevedahl@gmail.com","subject":"[PATCH] fixup git-gui: allow blame to show uncommitted changes","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-23T18:19:50Z","receivedAt":"2026-05-23T18:19:56Z","isPatch":true,"body":"Commit a0db0d61fb (\"git-gui: Generate blame on uncommitted working tree\nfile\", 2007-05-08) added ability for git-gui's blame to use uncommited\ncontent in the worktree in the blame display. The specific mechanism\nthat allows this is passing head as {} to the blame constructor, and this\nmode is enabled by specifying no head, which means the current branch is\nused, and no swapping of head / path is possible. Path checking looks for the\npath existing in the currently checked out branch, so files unknown to\ngit cannot be blamed. This mirrors git blame behavior. Both now will\n\n    use the worktree contents of the file $path as the basis for blame,\n        including showing an empty blame if the file exists and is empty.\n    error if $path is remove from the worktree, even if it exists on the\n          current branch.\n\nThe latter behavior is a change: before this patch, git-gui will show\nthe unknown file in its entirety with no annotations. The new behavior,\nfollowing git blame, reports that git knows nothing about the file.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\nThis patch will be squashed into 0011 in a v3, or kept separate. The\ncomment in patch 11 saying blame's use of the worktree is unaffected\nis wrong, this fixes blame to do exactly what git-blame does with\nworktree content. Slightly different than what git-gui did before\nregarding unknown files, but I think git-blame's approach is correct.\n\n\n git-gui.sh | 29 ++++++++++++++++++++++++-----\n 1 file changed, 24 insertions(+), 5 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex ae609f86f1..114511974a 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -3084,11 +3084,16 @@ blame {\n \t\t}\n \t}\n \n-\t# no swapping allowed if head not given, use current branch (HEAD)\n+\t# If head not given, use current branch (HEAD), no swapping allowed,\n+\t# and blame may use the worktree file content.\n+\tset use_worktree 0\n \tif {$head eq {}} {\n \t\tload_current_branch\n \t\tset head $current_branch\n \t\tset canswap 0\n+\t\tif {$subcommand eq {blame} && ![is_bare]} {\n+\t\t\tset use_worktree 1\n+\t\t}\n \t}\n \n \t# -- before \"rev\" arg means we got -- path head\n@@ -3098,7 +3103,16 @@ blame {\n \t\tset canswap 0\n \t}\n \n-\tset objtype [find_path_type $head $path]\n+\tif {$use_worktree} {\n+\t\tif {[file isfile $path]} {\n+\t\t\tset objtype {blob}\n+\t\t} else {\n+\t\t\tset objtype {}\n+\t\t}\n+\t} else {\n+\t\tset objtype [find_path_type $head $path]\n+\t}\n+\n \tif {$objtype eq {} && $canswap} {\n \t\tset objtype [find_path_type $althead $altpath]\n \t\tif {$objtype ne {}} {\n@@ -3108,7 +3122,7 @@ blame {\n \t}\n \tset current_branch $head\n \n-\t# check that path exists in head, and objtype matches need\n+\t# check objtype matches need\n \tif {$objtype ne $required_objtype} {\n \t\tswitch -- $required_objtype {\n \t\t\ttree {set err [strcat \\\n@@ -3130,8 +3144,13 @@ blame {\n \tbrowser {\n \t\tbrowser::new $head $path\n \t}\n-\tblame {\n-\t\tblame::new $head $path $jump_spec\n+\n+\tblame   {\n+\t\tif {$use_worktree} {\n+\t\t\tblame::new {} $path $jump_spec\n+\t\t} else {\n+\t\t\tblame::new $head $path $jump_spec\n+\t\t}\n \t}\n \t}\n \treturn\n-- \n2.54.0.99.14\n\n"},{"id":"543988","messageId":"8b4e8b73-eb2c-41bb-9653-08e38fedd434@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-11-mlevedahl@gmail.com","subject":"Re: [PATCH v2 10/11] git-gui: adapt blame/browser parsing for bare operation","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-23T20:31:37Z","receivedAt":"2026-05-23T20:31:42Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui's blame and browser subcommands do not work with bare\n> repositories, but they should per commit c52c94524b (\"git-gui: Allow\n> blame/browser subcommands on bare repositories\", 2007-07-17). Assuming\n> that commit worked, something changed since reintroducing a hard-coded\n> dependency upon a worktree.\n> \n> The basic issue goes back to 3e45ee1ef2 (\"git-gui: Smarter command line\n> parsing for browser, blame\", 2007-05-08), which seeks to implement\n> command line parsing similar to git blame. That commit introduces\n> depencies upon the worktree to decide which argument is rev or path.\n> \n> Looking at builtin/blame.c in git around line 1120:\n> \n> \t * (1) if dashdash_pos != 0, it is either\n> \t *     \"blame [revisions] -- <path>\" or\n> \t *     \"blame -- <path> <rev>\"\n> \t *\n> \t * (2) otherwise, it is one of the two:\n> \t *     \"blame [revisions] <path>\"\n> \t *     \"blame <path> <rev>\"\n> \n> shows the clear intent: rev and path may be swapped in input so both\n> meanings must be tried, but -- may be used to designate which is the\n> path forcing or precluding trying the swapped arguments.\n\nPlease do not use this code comment as recipe for our own argument\nparseing. In particular, that <path> can occur for <rev> goes back to\nthe initial implementation of git pickaxe in cee7f245dcae (\"git-pickaxe:\nblame rewritten.\", 2006-10-19). Since acca687fa9db (\"git-pickaxe: retire\npickaxe\", 2006-11-08), the documentation of git-blame states that <file>\nis always last (but the implementation was not adjusted accordingly).\n\nIn general, Git's argument parsing requires revisions before pathspec.\nTo disambiguate, '--' can be used. If it is not used, arguments are\ncheck whether they are files or refs, and as soon as one argument is\nidentified as file unambiguously, all later arguments must also be files.\n\nWe should follow this pattern, and to do that, we could just delegate\nargument processing to `git rev-parse`.\n\n> \n> With a worktree, git gui correctly swaps the arguments if the given path\n> exists in the worktree. git blame does this using the git repository.\n> But, git-gui sometimes interprets the -- to have an exactly opposite\n> meaning:\n> \n>     git blame       Makefile gitgui-0.19.0       works\n>     git gui blame   Makefile gitgui-0.19.0       works\n\nGit gui shows something, but ignores the ref, so doesn't quite work.\n\n> \n>     git blame       -- Makefile gitgui-0.19.0    works\n>     git gui blame   -- Makefile gitgui-0.19.0    works\n\nDitto.\n\n> \n>     git blame       Makefile -- gitgui-0.19.0    fails (correctly)\n>     git gui blame   Makefile -- gitgui-0.19.0    works (should fail)\n\nDitto.\n\n> \n>     git blame       gitgui-0.19.0 -- Makefile    works (correctly)\n>     git gui blame   gitgui-0.19.0 -- Makefile    fails (should work)\n\nYes, there's a bug in the argument parser that -- isn't skipped, but\ntreated as the file name.\n\n> \n> It is possible to patch the code to operate without a worktree, but this\n> will make the commands operate differently with and without a worktree,\n> won't fix the parsing issues above, and won't address the issues that\n> can arise when using a worktree to help decisions on a different rev\n> with file/directory conflicts, etc.\n\nBefore this patch 'git gui blame' can show contents uncommitted changes,\nbut with this patch this isn't possible. I see you have just sent a\npatch that may fix this, but I havn't looked at it, yet.\n\n> \n> So, let's rework the parser so that it uses -- as does git blame, and\n> uses git ls-tree to query the given revision for existence and type of\n> path rather than basing this upon a possibly unrelated worktree. Also,\n> abort early when the given path is not found, or does not match the need\n> (file or directory). This fixes some current cases where git-gui will\n> open a window with no content, possibly also with an error message.\n\nThere is no desire to make 'git gui blame' work the same with and\nwithout a working tree.\n\nIf we invoke git to help argument parsing, then it should be\n'rev-parse', not 'ls-tree'.\n\n> \n> This does not change whether or how git-gui uses staged and unstaged\n> content in the current worktree for blame display.\n> \n> Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>\n> ---\n\n>  \twm deiconify .\n>  \tswitch -- $subcommand {\n>  \tbrowser {\n> -\t\tif {$jump_spec ne {}} usage\n\nLet's keep this line, which diagnoses an incorrect --line= argument.\n\n> -\t\tif {$head eq {}} {\n> -\t\t\tif {$path ne {} && [file isdirectory $path]} {\n> -\t\t\t\tset head $current_branch\n> -\t\t\t} else {\n> -\t\t\t\tset head $path\n> -\t\t\t\tset path {}\n> -\t\t\t}\n> -\t\t}\n>  \t\tbrowser::new $head $path\n>  \t}\n> -\tblame   {\n> -\t\tif {$head eq {} && ![file exists $path]} {\n> -\t\t\tcatch {wm withdraw .}\n> -\t\t\ttk_messageBox \\\n> -\t\t\t\t-icon error \\\n> -\t\t\t\t-type ok \\\n> -\t\t\t\t-title [mc \"git-gui: fatal error\"] \\\n> -\t\t\t\t-message [mc \"fatal: cannot stat path %s: No such file or directory\" $path]\n> -\t\t\texit 1\n> -\t\t}\n> +\tblame {\n>  \t\tblame::new $head $path $jump_spec\n>  \t}\n>  \t}\n\n-- Hannes\n\n"},{"id":"543991","messageId":"03385a9e-bc94-4096-8fa5-7b8298269230@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-12-mlevedahl@gmail.com","subject":"Re: [PATCH v2 11/11] git-gui: add gui and pick as explicit subcommands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-24T07:00:30Z","receivedAt":"2026-05-24T07:00:42Z","isPatch":true,"body":"Am 20.05.26 um 22:24 schrieb Mark Levedahl:\n> git-gui accepts subcommands blame | browser | citool, and assumes the\n> subcommand is 'gui' if none is actually given, But, git-gui also has a\n> repository picker (choose_repository::pick) that can create a new\n> repository + worktree, or choose an existing one, switch to that, and\n> the run the gui. The user has no direct control over invoking the\n> picker, instead the picker is triggered by failure in the repository /\n> worktree discovery process: this includes being started in a directory\n> not controlled by git, which is probably the intended use case.\n> \n> The picker can appear when the user has no intention of creating a new\n> worktree, and the user cannot use the picker to create a new worktree\n> inside another.\n> \n> So, add two explicit subcommands:\n>     gui  - Run the gui if repository/worktree discovery succeeds, or die\n>            with an error message, but never run the picker.\n>     pick - First run the picker, regardless, then start the gui in\n>            the chosen worktree.\n> \n> Nothing in this changes the prior behavior, the alternates above must be\n> explicitly selected to see any change.\n\nGood.\n\n> @@ -1174,7 +1184,7 @@ proc unset_gitdir_vars {} {\n>  \n>  # find repository.\n>  set _gitdir {}\n> -if {$_gitdir eq {}} {\n> +if {[is_enabled gitdir_discovery]} {\n\nThis makes a factually unconditional branch into a conditional one.\n\n>  \tif {[catch {\n>  \t\tset _gitdir [git rev-parse --absolute-git-dir]\n>  \t} err]} {\n> @@ -1186,7 +1196,7 @@ if {$_gitdir eq {}} {\n>  }\n>  \n>  set picked 0\n> -if {$_gitdir eq {}} {\n> +if {$_gitdir eq {} && [is_enabled picker]} {\n>  \tunset_gitdir_vars\n>  \tload_config 1\n>  \tapply_config\n> @@ -1202,6 +1212,12 @@ if {$_gitdir eq {}} {\n>  \tset picked 1\n>  }\n>  \n> +if {$_gitdir eq {}} {\n> +\tcatch {wm withdraw .}\n> +\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n\nI wondered where this $err is filled in, and it can only be the error\nfrom a failed gitdir discovery. Good.\n\n> +\texit 1\n> +}\n> +\n>  # find worktree, continue without if not required\n>  if {[catch {\n>  \tset _gitworktree [git rev-parse --show-toplevel]\nThis looks good!\n\n-- Hannes\n\n"},{"id":"543992","messageId":"43f070e4-e624-4a33-8c24-294520fb503a@kdbg.org","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"Re: [PATCH v2 00/11] Improve git gui operation without a worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-05-24T07:16:33Z","receivedAt":"2026-05-24T07:16:38Z","isPatch":true,"body":"Am 20.05.26 um 22:23 schrieb Mark Levedahl:\n> git gui has a number of inter-related problems that result in problems\n> during startup from anything but a checked out worktree pointing at a\n> valid git repository. Some of the symptoms are:\n> - blame / browser subcommands, and launching gitk, are intended to be\n>   useful without a worktree, but fail to work.\n> - unlike git, git-gui is supposed to use the parent directory as a\n>   worktree if started from the .git subdirectory in the very common\n>   single worktree + embedded git repository format. This does not\n>   work.\n> - git-gui includes a repository picker allowing a user to select a\n>   worktree from a list and/or start a new repo+worktree: this dialog can\n>   appear at unexpected times, masking useful error feedback on\n>   configuration problems.\n> \n> This patch series addresses the above issues, substantially rewriting\n> the initial repository/worktree process to rely upon git rev-parse so\n> that git's knowledge of access rules, repository configuration, and use\n> of GIT_DIR / GIT_WORK_TREE (or git --gitdir / --work-tree) is used\n> throughout, replacing code largely based upon what git did in 2008. This\n> also means that git gui will naturally gain any new rules implmented in\n> git-core.\n> \n> With this, git-gui only exports GIT_WORK_TREE when non-empty.\n> GIT_WORK_TREE is needed, and must be exported, if the user is overriding\n> core.worktree in the git repository. But, GIT_WORK_TREE cannot be used\n> to specify the lack of a worktree, so exporting an empty GIT_WORK_TREE\n> is one of the problems fixed by this series.\n> \n> v2 of this series is a very substantial rewrite driven by j6t's review,\n> with patches reoranized and squashed, interfaces to the repository\n> chooser changed, a different code structure to allow user control of the\n> repository picker, a different approach to fixing the command line\n> parser for blame / browser, and other more minor changes. Patches\n> for fixing blame / browser are now after all discovery refactoring as\n> they cannot be tested without some of those fixes.\n> \n> Many subtle things are fixed beyond the list at the top, including\n> better compatibility with git blame and repeatable browser / blame\n> operation for specific revs not in the worktree, regardless of the\n> worktree state. j6t indicated that in the git-gui project, the following\n> fails in the current release:\n> \n> cd lib\n> GIT_DIR=$PWD/../.git GIT_WORK_TREE=$PWD/.. ../git-gui.sh browser origin/master .\n> \n> This is due to a _prefix issue, and is fixed as of the patch\n>      git-gui: use git rev-parse for worktree discovery\n> \n\nI've completed my review of this iteration.\n\nRepository and working tree discovery is already converging fast.\nHowever, I have issues with the proposed argument parsing of the browser\nand blame modes, in particular, I don't think that we need to\naccommodate the uncanny file-before-rev argument order and that it\ndisregards the worktree completely. Maybe we should postpone any changes\nin this area, if possible?\n\nThroughout, we use a strange indentation style of 'if {[catch ...' that\nis violated in new code, but I left uncommented. It should indent the\ncatch body one additional level like so:\n\n\tif {catch {\n\t\t\tcommands that can fail\n\t\t} err]} {\n\t\terror handling here\n\t}\n\nThank you very much for working on this topic.\n\n-- Hannes\n\n"},{"id":"544067","messageId":"3cb066ca-0d1f-4197-ae96-050e94db2453@gmail.com","threadId":"65531","inReplyTo":"43f070e4-e624-4a33-8c24-294520fb503a@kdbg.org","subject":"Re: [PATCH v2 00/11] Improve git gui operation without a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-25T16:02:10Z","receivedAt":"2026-05-25T16:02:12Z","isPatch":true,"body":"\n\nOn 5/24/26 3:16 AM, Johannes Sixt wrote:\n> Am 20.05.26 um 22:23 schrieb Mark Levedahl:\n> I've completed my review of this iteration.\n>\n> Repository and working tree discovery is already converging fast.\n> However, I have issues with the proposed argument parsing of the browser\n> and blame modes, in particular, I don't think that we need to\n> accommodate the uncanny file-before-rev argument order and that it\n> disregards the worktree completely. Maybe we should postpone any changes\n> in this area, if possible?\nI'm not willing to give up on browser/blame yet: making these work without a worktree was\nmy motivator to start this series. Ignoring implementation details, etc., the issues I see\nhere are:\n\nThe undocumented feature to accept rev / path or path / rev, as does git blame. The latter\nat least has a comment on what is expected in the code, but no mention of this exists in\nblames man-page, nor that of any other git command I've examined. I'd prefer to just\nremove it, I'll take your comment above as agreement in principal.\n\nbrowser and blame are both fundamentally about git history. Considering browser.tcl and\nblame.tcl, which produce the displays:\n\nbrowser shows only content from a tree in a commit. It never uses any information from a\nworktree. Given a non-existent path (or a non-existent rev),\nbrowser displays an empty window, not an error message.\n\nblame can take content from a file in a commit, or from the worktree as long as the file\nis in a commit. A modified file in the worktree has changed / added lines annotated as\nuncommitted work. But, given a file not in rev, blame displays the file with no\nannotations at all, not as uncommitted work, and no error message.\n\nSo. both blame and browser require that $path is contained in $rev, even if $rev defaults\nto HEAD.\nThe parser never checks this, though.\n\nThese commands, that *should* work fine without a worktree do not. and display confusing\ninformation (e.g., a blank browser window, or unannotated file) when a simple error\nmessage from the parser would convey more information.\n\n>\n> Throughout, we use a strange indentation style of 'if {[catch ...' that\n> is violated in new code, but I left uncommented. It should indent the\n> catch body one additional level like so:\n>\n> \tif {catch {\n> \t\t\tcommands that can fail\n> \t\t} err]} {\n> \t\terror handling here\n> \t}\nYes, just count the number of { - number of }. Vim's indent mode for tcl gets this very\nwrong. All fixed, I hope.\n>\n> Thank you very much for working on this topic.\nand thank you for the very thorough review.\n>\n> -- Hannes\n>\n\n"},{"id":"544320","messageId":"20260531230225.126817-1-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260520202411.108764-1-mlevedahl@gmail.com","subject":"[PATCH v3 00/12] Improve git gui operation without a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:13Z","receivedAt":"2026-05-31T23:02:36Z","isPatch":true,"body":"git gui has a number of inter-related problems that result in problems\nduring startup from anything but a checked out worktree pointing at a\nvalid git repository. Some of the symptoms are:\n- blame / browser subcommands, and launching gitk, are intended to be\n  useful without a worktree, but fail to work.\n- unlike git, git-gui is supposed to use the parent directory as a\n  worktree if started from the .git subdirectory in the very common\n  single worktree + embedded git repository format. This does not\n  work.\n- git-gui includes a repository picker allowing a user to select a\n  worktree from a list and/or start a new repo+worktree: this dialog can\n  appear at unexpected times, masking useful error feedback on\n  configuration problems.\n\nThis patch series addresses the above issues, substantially rewriting\nthe initial repository/worktree process to rely upon git rev-parse so\nthat git's knowledge of access rules, repository configuration, and use\nof GIT_DIR / GIT_WORK_TREE (or git --gitdir / --work-tree) is used\nthroughout, replacing code largely based upon what git did in 2008. This\nalso means that git gui will naturally gain any new rules implmented in\ngit-core.\n\nWith this, git-gui only exports GIT_WORK_TREE when non-empty.\nGIT_WORK_TREE is needed, and must be exported, if the user is overriding\ncore.worktree in the git repository. But, GIT_WORK_TREE cannot be used\nto specify the lack of a worktree, so exporting an empty GIT_WORK_TREE\nis one of the problems fixed by this series.\n\nv3 of this series addresses j6t's review of v2, with some reordering of\npatches (1 from j6t added, patch #8 moved to #1), adds another rewrite\nof the browser / blame parser that eliminates the notion of path before\nrev on the command line, blame works correctly with a user modified file\nin the worktree. Clarification is added on the need for GTI_WORK_TREE,\nand the logic in finding a worktree from the gitdir is simplified.\n\nJohannes Sixt (1):\n  git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk\n\nMark Levedahl (11):\n  git-gui: use HEAD as current branch when detached\n  git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE\n  git-gui: do not change global vars in choose_repository::pick\n  git-gui: use --absolute-git-dir\n  git-gui: use rev-parse exclusively to find a repository\n  git-gui: use git rev-parse for worktree discovery\n  git-gui: simplify [is_bare] to report if a worktree is known\n  git-gui: try harder to find worktree from gitdir\n  git-gui: allow specifying path '.' to the browser\n  git-gui: check browser/blame arguments carefully\n  git-gui: add gui and pick as explicit subcommands\n\n git-gui.sh                | 377 ++++++++++++++++++++++----------------\n lib/choose_repository.tcl |  21 +--\n 2 files changed, 223 insertions(+), 175 deletions(-)\n\nInterdiff against v2:\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 299c1a0292..933e72c9b2 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1114,33 +1114,29 @@ unset argv0dir\n ## repository setup\n \n proc find_worktree_from_gitdir {} {\n-\t# Directory 'parent' of a repository named 'parent/.git' might be the worktree.\n-\t# Assure parent is a worktree and using the git repository already discovered.\n-\t# Also, handle case of being in a worktree's gitdir, where file \"gitdir\" points to\n-\t# gitlink file .git in the real worktree.\n+\t# this is invoked only if the current directory is inside the repository\n \tset worktree {}\n \tif {[file tail $::_gitdir] eq {.git}} {\n+\t\t# the dir containing .git is a worktree if repo allows it\n+\t\t# Check that git reports parent as a worktree (gitdir might not allow a worktree)\n \t\tif {[catch {\n-\t\t\tset gitdir_parent [file dirname $::_gitdir]\n-\t\t\tset worktree [git -C $gitdir_parent rev-parse --show-toplevel]\n-\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n-\t\t\tif {$::_gitdir ne $parent_gitdir} {\n-\t\t\t\tset worktree {}\n-\t\t\t}\n-\t\t}]} {\n+\t\t\t\tset parent [file dirname $::_gitdir]\n+\t\t\t\tset worktree [git -C $parent rev-parse --show-toplevel]\n+\t\t\t}]} {\n \t\t\tset worktree {}\n \t\t}\n \t} elseif [file exists {gitdir}] {\n+\t\t# a worktree gitdir has .gitdir naming worktree/.git\n+\t\t# assure git run there reports this dir as the gitdir (links might be broken)\n \t\tif {[catch {\n-\t\t\tset fd_gitdir [open {gitdir} {r}]\n-\t\t\tset gitlink_parent [file dirname [read $fd_gitdir]]\n-\t\t\tcatch {close $fd_gitdir}\n-\t\t\tset worktree [git -C $gitlink_parent rev-parse --show-toplevel]\n-\t\t\tset parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n-\t\t\tif {$::_gitdir ne $parent_gitdir} {\n-\t\t\t\tset worktree {}\n-\t\t\t}\n-\t\t}]} {\n+\t\t\t\tset fd_gitdir [open {gitdir} {r}]\n+\t\t\t\tset worktree [file dirname [read $fd_gitdir]]\n+\t\t\t\tcatch {close $fd_gitdir}\n+\t\t\t\tset worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\t\tif {$::_gitdir ne $worktree_gitdir} {\n+\t\t\t\t\tset worktree {}\n+\t\t\t\t}\n+\t\t\t}]} {\n \t\t\tcatch {close $fd_gitdir}\n \t\t\tset worktree {}\n \t\t}\n@@ -1153,7 +1149,7 @@ proc is_gitvars_error {err} {\n \tset GIT_DIR {}\n \tset GIT_WORK_TREE {}\n \tcatch {set GIT_DIR $::env(GIT_DIR); set havevars 1}\n-\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE) ; set havevars 1}\n+\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE); set havevars 1}\n \n \tif {$havevars} {\n \t\tcatch {wm withdraw .}\n@@ -1168,9 +1164,7 @@ proc is_gitvars_error {err} {\n \n proc set_gitdir_vars {} {\n \tglobal _gitdir _gitworktree env\n-\tif {$_gitdir ne {}} {\n-\t\tset env(GIT_DIR) $_gitdir\n-\t}\n+\tset env(GIT_DIR) $_gitdir\n \tif {$_gitworktree ne {}} {\n \t\tset env(GIT_WORK_TREE) $_gitworktree\n \t}\n@@ -1182,12 +1176,12 @@ proc unset_gitdir_vars {} {\n \tcatch {unset env(GIT_WORK_TREE)}\n }\n \n-# find repository.\n+# find repository\n set _gitdir {}\n if {[is_enabled gitdir_discovery]} {\n \tif {[catch {\n-\t\tset _gitdir [git rev-parse --absolute-git-dir]\n-\t} err]} {\n+\t\t\tset _gitdir [git rev-parse --absolute-git-dir]\n+\t\t} err]} {\n \t\tif {[is_gitvars_error $err]} {\n \t\t\texit 1\n \t\t}\n@@ -1200,14 +1194,13 @@ if {$_gitdir eq {} && [is_enabled picker]} {\n \tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n-\tif {![choose_repository::pick]} {\n-\t\texit 1\n-\t}\n+\tchoose_repository::pick\n \tif {[catch {\n-\t\tset _gitdir [git rev-parse --absolute-git-dir]\n-\t} err]} {\n+\t\t\tset _gitdir [git rev-parse --absolute-git-dir]\n+\t\t} err]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \"\\n\\n$err\"]\n+\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \\n\\n$err\"]\n+\t\texit 1\n \t}\n \tset picked 1\n }\n@@ -1220,9 +1213,9 @@ if {$_gitdir eq {}} {\n \n # find worktree, continue without if not required\n if {[catch {\n-\tset _gitworktree [git rev-parse --show-toplevel]\n-\tset _prefix [git rev-parse --show-prefix]\n-} err]} {\n+\t\tset _gitworktree [git rev-parse --show-toplevel]\n+\t\tset _prefix [git rev-parse --show-prefix]\n+\t} err]} {\n \tif {[is_gitvars_error $err]} {\n \t\texit 1\n \t}\n@@ -1238,17 +1231,14 @@ if {[is_bare]} {\n }\n \n if {![is_bare]} {\n-\tif {[catch {\n-\t\tcd $_gitworktree\n-\t} err]} {\n+\tif {[catch {cd $_gitworktree} err]} {\n \t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot change to discovered worktree: \"] \\\n-\t\t\t\"$_gitworktree\" \"\\n\\n$err\"]\n-\t\texit 1;\n+\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n+\t\texit 1\n \t}\n } elseif {![is_enabled bare]} {\n \tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n\" $_gitdir]\n+\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n \texit 1\n }\n \n@@ -2086,11 +2076,7 @@ proc do_gitk {revs {is_submodule false}} {\n \t} else {\n \t\tset pwd [pwd]\n \n-\t\tif {!$is_submodule} {\n-\t\t\tif {![is_bare]} {\n-\t\t\t\tcd $_gitworktree\n-\t\t\t}\n-\t\t} else {\n+\t\tif {$is_submodule} {\n \t\t\tcd $current_diff_path\n \t\t\tif {$revs eq {--}} {\n \t\t\t\tset s $file_states($current_diff_path)\n@@ -3026,26 +3012,18 @@ proc normalize_relpath {path} {\n \tif {$elements ne {}} {\n \t\treturn [eval file join $elements]\n \t} else {\n-\t\treturn {./}\n+\t\treturn {.}\n \t}\n }\n \n-proc find_path_type {head path} {\n-\tif {$path eq {./}} {\n-\t\t# the root-tree exists in every rev, ls-tree gives data on the contents,\n-\t\t# not the type of tree itself. So, if the rev exists, return {tree}\n-\t\tif {[catch {set objtype [git ls-tree $head]}]} {\n-\t\t\tset objtype {}\n-\t\t} else {\n-\t\t\tset objtype {tree}\n-\t\t}\n+proc show_parse_err {err} {\n+\tif {[tk windowingsystem] eq \"win32\"} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup $err\n \t} else {\n-\t\t# test that the path exists in head, ls-tree gives info on the path only\n-\t\tif {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {\n-\t\t\tset objtype {}\n-\t\t}\n+\t\tputs stderr $err\n \t}\n-\treturn $objtype\n+\texit 1\n }\n \n # -- Not a normal commit type invocation?  Do that instead!\n@@ -3054,91 +3032,80 @@ switch -- $subcommand {\n browser -\n blame {\n \tif {$subcommand eq \"blame\"} {\n-\t\tset subcommand_args {[--line=<num>] <[rev] [--] filename | [--] filename rev>}\n-\t\tset required_objtype blob\n+\t\tset subcommand_args {[--line=<num>] [rev] [--] <filename>}\n+\t\tset required_pathtype blob\n \t} else {\n-\t\tset subcommand_args {<[rev] [--] directory | [--] directory rev>}\n-\t\tset required_objtype tree\n+\t\tset subcommand_args {[rev] [--] <dirname>}\n+\t\tset required_pathtype tree\n \t}\n \tset maxargs [llength $subcommand_args]\n \tset nargs [llength $argv]\n \tif {$nargs < 1 || $nargs > $maxargs} usage\n \tset head {}\n-\tset althead {}\n \tset path {}\n-\tset altpath {}\n-\tset canswap 1\n \tset jump_spec {}\n \n-\t# assume: [--line=num] [head] [--] path as the possible arguments, in order.\n-\t# head and path may need a swap later.\n-\tfor {set iarg 0} {$iarg < $nargs} {incr iarg} {\n-\t\tset arg [lindex $argv $iarg]\n-\t\tif {$arg eq {--}} {\n-\t\t\t# next arg is the path, prevent or FORCE swap?\n-\t\t\tif {$iarg == $nargs - 2} {\n-\t\t\t\tset canswap 0\n-\t\t\t} elseif {$iarg == $nargs - 3} {\n-\t\t\t\tset canswap 2\n-\t\t\t} else {\n+\tset iarg 0\n+\tforeach a $argv {\n+\t\tincr iarg\n+\t\tif {$iarg == $nargs} {\n+\t\t\t# final argument is path\n+\t\t\tset path [normalize_relpath [file join $_prefix $a]]\n+\t\t} elseif {$a eq {--}} {\n+\t\t\t# allow before required final arg that must be path\n+\t\t\tif {$iarg != $nargs - 1} {\n \t\t\t\tusage\n \t\t\t}\n-\t\t} elseif {[regexp {^--line=(\\d+)$} $arg arg lnum]} {\n+\t\t} elseif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n \t\t\t# --line can only be the first arg\n-\t\t\tif {$iarg != 0 || $maxargs < 4} usage\n+\t\t\tif {$iarg != 1 || $subcommand ne {blame}} usage\n \t\t\tset jump_spec [list $lnum]\n-\t\t} elseif {$iarg == $nargs - 1} {\n-\t\t\t# assume final argument is path\n-\t\t\tset path [normalize_relpath [file join $_prefix $arg]]\n-\t\t\tset althead $arg\n \t\t} elseif {$head eq {}} {\n-\t\t\t# assume the other argument is head\n-\t\t\tset head $arg\n-\t\t\tset altpath [normalize_relpath [file join $_prefix $arg]]\n+\t\t\tset head $a\n \t\t} else {\n \t\t\tusage\n \t\t}\n \t}\n \n-\t# no swapping allowed if head not given, use current branch (HEAD)\n+\t# If head not given, use current branch (HEAD),\n+\t# and blame will use worktree if there is one.\n+\tset use_worktree 0\n \tif {$head eq {}} {\n \t\tload_current_branch\n \t\tset head $current_branch\n-\t\tset canswap 0\n-\t}\n-\n-\t# -- before \"rev\" arg means we got -- path head\n-\tif {$canswap == 2} {\n-\t\tset head $althead\n-\t\tset path $altpath\n-\t\tset canswap 0\n-\t}\n-\n-\tset objtype [find_path_type $head $path]\n-\tif {$objtype eq {} && $canswap} {\n-\t\tset objtype [find_path_type $althead $altpath]\n-\t\tif {$objtype ne {}} {\n-\t\t\tset head $althead\n-\t\t\tset path $altpath\n+\t\tif {$subcommand eq {blame} && ![is_bare]} {\n+\t\t\tif {![file isfile $path]} {\n+\t\t\t\tshow_parse_err [mc \"fatal: no such file '%s' in worktree\" $path]\n+\t\t\t}\n+\t\t\tset use_worktree 1\n \t\t}\n-\t}\n-\tset current_branch $head\n-\n-\t# check that path exists in head, and objtype matches need\n-\tif {$objtype ne $required_objtype} {\n-\t\tswitch -- $required_objtype {\n-\t\t\ttree {set err [strcat \\\n-\t\t\t\t[mc \"'%s' is not a directory in rev '%s'\" $path $head]]}\n-\t\t\tblob {set err [strcat \\\n-\t\t\t\t[mc \"'%s' is not a filename in rev '%s'\" $path $head]]}\n-\t\t}\n-\t\tif {[tk windowingsystem] eq \"win32\"} {\n-\t\t\tcatch {wm withdraw .}\n-\t\t\terror_popup $err\n+\t} else {\n+\t\tif {[catch {\n+\t\t\t\tset commitid \\\n+\t\t\t\t\t[git rev-parse --verify --end-of-options \\\n+\t\t\t\t\t[strcat $head \"^{commit}\"]]\n+\t\t\t}]} {\n+\t\t\tshow_parse_err [mc \"fatal: '%s' is not a valid rev'\" $head]\n \t\t} else {\n-\t\t\tputs stderr $err\n+\t\t\tset current_branch $head\n+\t\t}\n+\t}\n+\n+\t# check path is known in head, and is file / directory as required\n+\tset pathtype {}\n+\tcatch {set pathtype [git ls-tree {--format=%(objecttype)} $head $path]}\n+\tif {$pathtype ne {} && $path eq {.}} {\n+\t\t# ls-tree gives contents of root-dir, we need root-dir itself\n+\t\tset pathtype {tree}\n+\t}\n+\n+\tif {$pathtype ne $required_pathtype} {\n+\t\tswitch -- $required_pathtype {\n+\t\t\ttree {show_parse_err \\\n+\t\t\t\t[mc \"'%s' is not a directory in rev '%s'\" $path $head]}\n+\t\t\tblob {show_parse_err \\\n+\t\t\t\t[mc \"'%s' is not a filename in rev '%s'\" $path $head]}\n \t\t}\n-\t\texit 1\n \t}\n \n \twm deiconify .\n@@ -3146,8 +3113,8 @@ blame {\n \tbrowser {\n \t\tbrowser::new $head $path\n \t}\n-\tblame {\n-\t\tblame::new $head $path $jump_spec\n+\tblame   {\n+\t\tblame::new [expr {$use_worktree ? {} : $head}] $path $jump_spec\n \t}\n \t}\n \treturn\n-- \n2.54.0.99.14\n\n"},{"id":"544321","messageId":"20260531230225.126817-2-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 01/12] git-gui: use HEAD as current branch when detached","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:14Z","receivedAt":"2026-05-31T23:03:00Z","isPatch":true,"body":"commit f87a36b697 (\"git-gui: use git-branch --show-current\", 2024-02-12)\nchanged git-gui to use git-branch to access refs, rather than directly\nreading files as doing the latter is not compatible with the reftable\nbackend. git branch --show-current reports an empty branch name when the\nhead is detached, and in this case load_current_branch needs to report\nHEAD using special case logic as it did prior to the above commit. Make\nit do so.\n\nThis addresses an issue with git-gui browser failing with a detached\nhead.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 23fe76e498..f70a54a61b 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -670,6 +670,9 @@ proc load_current_branch {} {\n \n \tset current_branch [git branch --show-current]\n \tset is_detached [expr [string length $current_branch] == 0]\n+\tif {$is_detached} {\n+\t\tset current_branch {HEAD}\n+\t}\n }\n \n auto_load tk_optionMenu\n-- \n2.54.0.99.14\n\n"},{"id":"544322","messageId":"20260531230225.126817-3-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 02/12] git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:15Z","receivedAt":"2026-05-31T23:03:01Z","isPatch":true,"body":"From: Johannes Sixt <j6t@kdbg.org>\n\nIn the procedure that invokes Gitk, we have a 'cd $_gitworktree'. Such\na change of the current directory is not necessary, because\n\n- if we have a working tree, then the startup routine has already\n  changed the current directory to the root of the working tree, which\n  *is* $_gitworktree; or\n\n- if we are in a bare repository, then there is no point in changing\n  the current directory anywhere. (And $_gitworktree is empty.)\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 6 +-----\n 1 file changed, 1 insertion(+), 5 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex f70a54a61b..52897fbd09 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2024,11 +2024,7 @@ proc do_gitk {revs {is_submodule false}} {\n \n \t\tset pwd [pwd]\n \n-\t\tif {!$is_submodule} {\n-\t\t\tif {![is_bare]} {\n-\t\t\t\tcd $_gitworktree\n-\t\t\t}\n-\t\t} else {\n+\t\tif {$is_submodule} {\n \t\t\tcd $current_diff_path\n \t\t\tif {$revs eq {--}} {\n \t\t\t\tset s $file_states($current_diff_path)\n-- \n2.54.0.99.14\n\n"},{"id":"544323","messageId":"20260531230225.126817-4-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 03/12] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:16Z","receivedAt":"2026-05-31T23:03:02Z","isPatch":true,"body":"git-gui unconditionally exports _gitdir as GIT_DIR, and _gitworktree as\nGIT_WORK_TREE, to the environment, and unconditionally\nunsets these environment variables before invoking gitk or git-gui when\na submodule is involved. This export happens even if _gitworktree is\nempty, which happens when running from a bare repository. However,\nexporting GIT_WORK_TREE as empty is never valid, and causes errors in\ngit.\n\nGIT_DIR must be exported if the repository is not discoverable from the\nworktree (or current directory if there is no worktree). The user might\nhave configured this.\n\nIf there is a worktree, git-gui makes this the current directory.\nHowever, if the repository sets core.worktree, this value can only be\noverridden by GIT_WORK_TREE so the latter must be exported.\n\nAs we cannot eliminate conditions where either variable is needed, let's\nimplement a pair of functions to set / unset these variables without\nerror, and without ever exporting an empty GIT_WORK_TREE.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 37 +++++++++++++++++++++----------------\n 1 file changed, 21 insertions(+), 16 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 52897fbd09..2cf14d0dd5 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1125,6 +1125,20 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc set_gitdir_vars {} {\n+\tglobal _gitdir _gitworktree env\n+\tset env(GIT_DIR) $_gitdir\n+\tif {$_gitworktree ne {}} {\n+\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t}\n+}\n+\n+proc unset_gitdir_vars {} {\n+\tglobal env\n+\tcatch {unset env(GIT_DIR)}\n+\tcatch {unset env(GIT_WORK_TREE)}\n+}\n+\n set picked 0\n if {[catch {\n \t\tset _gitdir $env(GIT_DIR)\n@@ -1210,8 +1224,8 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-set env(GIT_DIR) $_gitdir\n-set env(GIT_WORK_TREE) $_gitworktree\n+# Export the final paths\n+set_gitdir_vars\n \n ######################################################################\n ##\n@@ -2010,7 +2024,7 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitdir _gitworktree\n+\tglobal _gitworktree\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n@@ -2020,8 +2034,6 @@ proc do_gitk {revs {is_submodule false}} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find gitk in PATH\"]\n \t} else {\n-\t\tglobal env\n-\n \t\tset pwd [pwd]\n \n \t\tif {$is_submodule} {\n@@ -2049,13 +2061,11 @@ proc do_gitk {revs {is_submodule false}} {\n \t\t\t# TODO we could make life easier (start up faster?) for gitk\n \t\t\t# by setting these to the appropriate values to allow gitk\n \t\t\t# to skip the heuristics to find their proper value\n-\t\t\tunset env(GIT_DIR)\n-\t\t\tunset env(GIT_WORK_TREE)\n+\t\t\tunset_gitdir_vars\n \t\t}\n \t\tsafe_exec_bg [concat $cmd $revs \"--\" \"--\"]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tif {[info exists main_status]} {\n@@ -2078,21 +2088,16 @@ proc do_git_gui {} {\n \tif {$exe eq {}} {\n \t\terror_popup [mc \"Couldn't find git gui in PATH\"]\n \t} else {\n-\t\tglobal env\n-\t\tglobal _gitdir _gitworktree\n-\n \t\t# see note in do_gitk about unsetting these vars when\n \t\t# running tools in a submodule\n-\t\tunset env(GIT_DIR)\n-\t\tunset env(GIT_WORK_TREE)\n+\t\tunset_gitdir_vars\n \n \t\tset pwd [pwd]\n \t\tcd $current_diff_path\n \n \t\tsafe_exec_bg [concat $exe gui]\n \n-\t\tset env(GIT_DIR) $_gitdir\n-\t\tset env(GIT_WORK_TREE) $_gitworktree\n+\t\tset_gitdir_vars\n \t\tcd $pwd\n \n \t\tset status_operation [$::main_status \\\n-- \n2.54.0.99.14\n\n"},{"id":"544324","messageId":"20260531230225.126817-5-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 04/12] git-gui: do not change global vars in choose_repository::pick","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:17Z","receivedAt":"2026-05-31T23:03:03Z","isPatch":true,"body":"The repository picker (choose_repository::pick, AKA pick) on success\nalways returns with the current directory at the root of the selected\nworktree, with the global variable _gitdir holding the name of the\ngit repository, possibly as a relative path, and _prefix {}. The\nworktree root (_gitworktree) is not filled out, and if the selection was\nfrom the \"recent\" list, no validation has occurred beyond testing that\nthe worktree root exists. So, repository and worktree validation are\nstill needed to be sure the new repo + worktree is usable.\n\npick only supports worktrees with a .git entry in the worktree root, so\ngit repository and worktree discovery will work starting in the current\ndirectory on return. In cases of error, or user abort, pick exits the\nprocess rather than returning.\n\nSo, let's change pick to not alter any global values, with success\nindicated by the process returning to the caller. In this case, the\ncurrent directory is the worktree root, with a .git entry. The caller\nthen proceeds with normal discovery to find and validate both repository\nand worktree.\n\nWith this, pick now returns 1 in the success case, but additional work\nwould be necessary to return from conditions where 0 should be returned.\nChecking this return value would be superfluous.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh                |  7 ++++++-\n lib/choose_repository.tcl | 21 ++++++++-------------\n 2 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 2cf14d0dd5..44914bddcf 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1153,9 +1153,14 @@ if {[catch {\n \tload_config 1\n \tapply_config\n \tchoose_repository::pick\n-\tif {![file isdirectory $_gitdir]} {\n+\tif {[catch {\n+\t\t\tset _gitdir [git rev-parse --git-dir]\n+\t\t} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \\n\\n$err\"]\n \t\texit 1\n \t}\n+\tset _prefix {}\n \tset picked 1\n }\n \ndiff --git a/lib/choose_repository.tcl b/lib/choose_repository.tcl\nindex 7e1462a20c..4b06afee93 100644\n--- a/lib/choose_repository.tcl\n+++ b/lib/choose_repository.tcl\n@@ -15,7 +15,7 @@ field w_recentlist ; # Listbox containing recent repositories\n field w_localpath  ; # Entry widget bound to local_path\n \n field done              0 ; # Finished picking the repository?\n-field clone_ok      false ; # clone succeeeded\n+field pick_ok           0 ; # true if repo pick/clone succeeded\n field local_path       {} ; # Where this repository is locally\n field origin_url       {} ; # Where we are cloning from\n field origin_name  origin ; # What we shall call 'origin'\n@@ -220,6 +220,8 @@ constructor pick {} {\n \tif {$top eq {.}} {\n \t\teval destroy [winfo children $top]\n \t}\n+\n+\treturn $pick_ok\n }\n \n method _center {} {\n@@ -327,8 +329,7 @@ method _git_init {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir .git\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \treturn 1\n }\n \n@@ -409,6 +410,7 @@ method _do_new2 {} {\n \tif {![_git_init $this]} {\n \t\treturn\n \t}\n+\tset pick_ok 1\n \tset done 1\n }\n \n@@ -621,7 +623,7 @@ method _do_clone2 {} {\n \t}\n \n \ttkwait variable @done\n-\tif {!$clone_ok} {\n+\tif {!$pick_ok} {\n \t\terror_popup [mc \"Clone failed.\"]\n \t\treturn\n \t}\n@@ -632,18 +634,12 @@ method _do_clone2_done {ok} {\n \tif {$ok} {\n \t\tif {[catch {\n \t\t\tcd $local_path\n-\t\t\tset ::_gitdir .git\n-\t\t\tset ::_prefix {}\n \t\t\t_append_recentrepos [pwd]\n \t\t} err]} {\n \t\t\tset ok 0\n \t\t}\n \t}\n-\tif {!$ok} {\n-\t\tset ::_gitdir {}\n-\t\tset ::_prefix {}\n-\t}\n-\tset clone_ok $ok\n+\tset pick_ok $ok\n \tset done 1\n }\n \n@@ -721,8 +717,7 @@ method _do_open2 {} {\n \t}\n \n \t_append_recentrepos [pwd]\n-\tset ::_gitdir $actualgit\n-\tset ::_prefix {}\n+\tset pick_ok 1\n \tset done 1\n }\n \n-- \n2.54.0.99.14\n\n"},{"id":"544325","messageId":"20260531230225.126817-6-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 05/12] git-gui: use --absolute-git-dir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:18Z","receivedAt":"2026-05-31T23:03:04Z","isPatch":true,"body":"git-gui uses git rev-parse --git-dir to get the pathname of the\ndiscovered git repository. The returned value can be relative, and is\n'.' if the current directory is the top of the repository directory\nitself.  git-gui has code to change '.' to [pwd] in this case so that\nsubsequent logic runs.\n\nBut, git rev-parse supports --absolute-git-dir from fac60b8925\n(\"rev-parse: add option for absolute or relative path formatting\",\n2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so\nthis more useful form is always available. Use --absolute-git-dir to\nalways get an absolute path, avoiding the need for other checks, and\ndelete the now unneeded code to fix a relative _gitdir.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 14 +++-----------\n 1 file changed, 3 insertions(+), 11 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 44914bddcf..8a5f0bd938 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1145,16 +1145,15 @@ if {[catch {\n \t\tset _prefix {}\n \t\t}]\n \t&& [catch {\n-\t\t# beware that from the .git dir this sets _gitdir to .\n-\t\t# and _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --git-dir]\n+\t\t# beware that from the .git dir this sets _prefix to the empty string\n+\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t\tset _prefix [git rev-parse --show-prefix]\n \t} err]} {\n \tload_config 1\n \tapply_config\n \tchoose_repository::pick\n \tif {[catch {\n-\t\t\tset _gitdir [git rev-parse --git-dir]\n+\t\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t\t} err]} {\n \t\tcatch {wm withdraw .}\n \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \\n\\n$err\"]\n@@ -1175,13 +1174,6 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-# we expand the _gitdir when it's just a single dot (i.e. when we're being\n-# run from the .git dir itself) lest the routines to find the worktree\n-# get confused\n-if {$_gitdir eq \".\"} {\n-\tset _gitdir [pwd]\n-}\n-\n if {![file isdirectory $_gitdir]} {\n \tcatch {wm withdraw .}\n \terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-- \n2.54.0.99.14\n\n"},{"id":"544326","messageId":"20260531230225.126817-7-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 06/12] git-gui: use rev-parse exclusively to find a repository","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:19Z","receivedAt":"2026-05-31T23:03:05Z","isPatch":true,"body":"git-gui attempts to use env(GIT_DIR) directly as the git repository,\naccepting GIT_DIR if it is a directory. Only if that fails is git\nrev-parse used to discover the repository.  But, this avoids all of\ngit-core's validity checking on a repository, thus possibly deferring an\nerror to a later step, possibly unexpected. Repository validation should\nbe part of initial setup so that later processing does not need error\ntrapping for configuration errors.\n\nLet's just invoke rev-parse so all error checking is done.\n\nWhile here, let's cleanup the error handling.\n\nStop if an error occurs and the user set GIT_DIR or GIT_WORK_TREE.\nUse of either or both of those variables is supported by git, but their\nuse also means the user has taken responsibility that they are correct,\nso a failure is something the user must address.\n\nOtherwise on error, continue the existing behavior and show the\nrepository picker. But, let's move the possible invocation of\nrepository_chooser::pick to a separate code block. This permits adding\nseparate conditions on using pick independent of repository discovery, and\nwill be exploited later in the series.  Note that the picker always\nreturns with the current directory in the root of a worktree with the\ngit repository is in the .git subdirectory.  The variable \"picked\" is\nused by git-gui to automatically execute the \"Explore Working Copy\" menu\nitem after the repository picker is run.  This is controlled by config\nvariable gui.autoexplore, and happens after all discovery is complete.\n\nRemove a later check on whether _gitdir is a directory: that code\ncannot be reached without rev-parse already validating the repository.\n\n_prefix is set as part of worktree discovery, but must be {} if not\nrunning with a worktree. Initialze this as {} along with other global\nvariables, this is the correct value is no worktree is found.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 49 ++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 34 insertions(+), 15 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 8a5f0bd938..aed3bc7200 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -374,6 +374,7 @@ set _gitdir {}\n set _gitworktree {}\n set _isbare {}\n set _githtmldir {}\n+set _prefix {}\n set _reponame {}\n set _shellpath {@@SHELL_PATH@@}\n \n@@ -1125,6 +1126,24 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc is_gitvars_error {err} {\n+\tset havevars 0\n+\tset GIT_DIR {}\n+\tset GIT_WORK_TREE {}\n+\tcatch {set GIT_DIR $::env(GIT_DIR); set havevars 1}\n+\tcatch {set GIT_WORK_TREE $::env(GIT_WORK_TREE); set havevars 1}\n+\n+\tif {$havevars} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"Invalid configuration:\"] \\\n+\t\t   \"\\n\" \"GIT_DIR: \" $GIT_DIR \\\n+\t\t   \"\\n\" \"GIT_WORK_TREE: \" $GIT_WORK_TREE \\\n+\t\t\t\"\\n\\n$err\"]\n+\t\treturn 1\n+\t}\n+\treturn 0\n+}\n+\n proc set_gitdir_vars {} {\n \tglobal _gitdir _gitworktree env\n \tset env(GIT_DIR) $_gitdir\n@@ -1139,16 +1158,22 @@ proc unset_gitdir_vars {} {\n \tcatch {unset env(GIT_WORK_TREE)}\n }\n \n+# find repository\n+set _gitdir {}\n+if {$_gitdir eq {}} {\n+\tif {[catch {\n+\t\t\tset _gitdir [git rev-parse --absolute-git-dir]\n+\t\t} err]} {\n+\t\tif {[is_gitvars_error $err]} {\n+\t\t\texit 1\n+\t\t}\n+\t\tset _gitdir {}\n+\t}\n+}\n+\n set picked 0\n-if {[catch {\n-\t\tset _gitdir $env(GIT_DIR)\n-\t\tset _prefix {}\n-\t\t}]\n-\t&& [catch {\n-\t\t# beware that from the .git dir this sets _prefix to the empty string\n-\t\tset _gitdir [git rev-parse --absolute-git-dir]\n-\t\tset _prefix [git rev-parse --show-prefix]\n-\t} err]} {\n+if {$_gitdir eq {}} {\n+\tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n \tchoose_repository::pick\n@@ -1159,7 +1184,6 @@ if {[catch {\n \t\terror_popup [strcat [mc \"Unusable repo/worktree:\"] \" [pwd] \\n\\n$err\"]\n \t\texit 1\n \t}\n-\tset _prefix {}\n \tset picked 1\n }\n \n@@ -1174,11 +1198,6 @@ if {$hashalgorithm eq \"sha1\"} {\n \texit 1\n }\n \n-if {![file isdirectory $_gitdir]} {\n-\tcatch {wm withdraw .}\n-\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$_gitdir\"]\n-\texit 1\n-}\n # _gitdir exists, so try loading the config\n load_config 0\n apply_config\n-- \n2.54.0.99.14\n\n"},{"id":"544327","messageId":"20260531230225.126817-8-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 07/12] git-gui: use git rev-parse for worktree discovery","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:20Z","receivedAt":"2026-05-31T23:03:06Z","isPatch":true,"body":"git gui uses a combination of tcl code and git invocations to determine\nthe worktree and the location with respect to the worktree root\n(_prefix). But, git rev-parse provides all of this information directly,\nand assures full error and configuration checking are done by git\nitself. The entirety of discovery in normal configurations involves\n\n\tgit rev-parse --show-toplevel (gets worktree root)\n\tgit rev-parse --show-prefix (shows location wrt the root)\n\nAn error thrown on either of these lines means the worktree discovered\nby git is unusable, or git did not discover a worktree because the\ncurrent directory is inside the repository. If the user has defined\nGIT_DIR or GIT_WORK_TREE, this is a user configuration error and git-gui\nshould stop.\n\nOtherwise, the blame or browser subcommands can be used without a\nworktree.\n\nA separate error might occur when changing to the root of the discovered\nworktree. The cause would be file system related and completely outside\nof git's control, so trap that independently.\n\nDiscovery of the repository and the worktree must be guarded to trap\nerrors: the intent is that any configuration problems are caught during\ndiscovery, and later processing need not include error trapping and\nrecovery. So, move all worktree discovery code to be immediately after\nrepository discovery.\n\nThis does move configuration loading to occur after worktree discovery\nrather than before. None of the code executed in worktree discovery has\nany option controlled by a git-gui configuration variable, so no impact\nis expected. git itself will always read the repository configuration,\nincluding worktree specific configuration data if that exists, so this\nis unaffected by when git-gui loads its own config data. Also, we cannot\nbe sure the worktree dependent configuration can be loaded before\nfull discovery is complete.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 61 ++++++++++++++++++++++++------------------------------\n 1 file changed, 27 insertions(+), 34 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex aed3bc7200..53b6859345 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1187,6 +1187,33 @@ if {$_gitdir eq {}} {\n \tset picked 1\n }\n \n+# find worktree, continue without if not required\n+if {[catch {\n+\t\tset _gitworktree [git rev-parse --show-toplevel]\n+\t\tset _prefix [git rev-parse --show-prefix]\n+\t} err]} {\n+\tif {[is_gitvars_error $err]} {\n+\t\texit 1\n+\t}\n+\tset _gitworktree {}\n+\tset _prefix {}\n+}\n+\n+if {![is_bare]} {\n+\tif {[catch {cd $_gitworktree} err]} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n+\t\texit 1\n+\t}\n+} elseif {![is_enabled bare]} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n+\texit 1\n+}\n+\n+# repository and worktree config are complete, export them\n+set_gitdir_vars\n+\n # Use object format as hash algorithm (either \"sha1\" or \"sha256\")\n set hashalgorithm [git rev-parse --show-object-format]\n if {$hashalgorithm eq \"sha1\"} {\n@@ -1202,37 +1229,6 @@ if {$hashalgorithm eq \"sha1\"} {\n load_config 0\n apply_config\n \n-set _gitworktree [git rev-parse --show-toplevel]\n-\n-if {$_prefix ne {}} {\n-\tif {$_gitworktree eq {}} {\n-\t\tregsub -all {[^/]+/} $_prefix ../ cdup\n-\t} else {\n-\t\tset cdup $_gitworktree\n-\t}\n-\tif {[catch {cd $cdup} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot move to top of working directory:\"] \"\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-\tunset cdup\n-} elseif {![is_enabled bare]} {\n-\tif {[is_bare]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"Cannot use bare repository:\"] \"\\n\\n$_gitdir\"]\n-\t\texit 1\n-\t}\n-\tif {$_gitworktree eq {}} {\n-\t\tset _gitworktree [file dirname $_gitdir]\n-\t}\n-\tif {[catch {cd $_gitworktree} err]} {\n-\t\tcatch {wm withdraw .}\n-\t\terror_popup [strcat [mc \"No working directory\"] \" $_gitworktree:\\n\\n$err\"]\n-\t\texit 1\n-\t}\n-\tset _gitworktree [pwd]\n-}\n set _reponame [file split [file normalize $_gitdir]]\n if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end-1]\n@@ -1240,9 +1236,6 @@ if {[lindex $_reponame end] eq {.git}} {\n \tset _reponame [lindex $_reponame end]\n }\n \n-# Export the final paths\n-set_gitdir_vars\n-\n ######################################################################\n ##\n ## global init\n-- \n2.54.0.99.14\n\n"},{"id":"544328","messageId":"20260531230225.126817-9-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 08/12] git-gui: simplify [is_bare] to report if a worktree is known","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:21Z","receivedAt":"2026-05-31T23:03:07Z","isPatch":true,"body":"git-gui includes proc is_bare, used in several places to make decisions\non whether a worktree exists, but also in discovery to tell if a\nworktree can be supported.\n\nBut, is_bare is out of date with regard to multiple worktrees, safe\nrepository guards, and possibly other relevant features known to git\nrev-parse. Also, is_bare caches its result on the first call, so is not\nuseful if a later step in the discovery process finds a worktree.\n\nSo, simplify is_bare to report whether git-gui has a worktree or is\nworking only from a repository.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 25 +------------------------\n 1 file changed, 1 insertion(+), 24 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 53b6859345..abee222903 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -372,7 +372,6 @@ if {[tk windowingsystem] eq \"aqua\"} {\n set _appname {Git Gui}\n set _gitdir {}\n set _gitworktree {}\n-set _isbare {}\n set _githtmldir {}\n set _prefix {}\n set _reponame {}\n@@ -524,29 +523,7 @@ proc get_config {name} {\n }\n \n proc is_bare {} {\n-\tglobal _isbare\n-\tglobal _gitdir\n-\tglobal _gitworktree\n-\n-\tif {$_isbare eq {}} {\n-\t\tif {[catch {\n-\t\t\tset _bare [git rev-parse --is-bare-repository]\n-\t\t\tswitch  -- $_bare {\n-\t\t\ttrue { set _isbare 1 }\n-\t\t\tfalse { set _isbare 0}\n-\t\t\tdefault { throw }\n-\t\t\t}\n-\t\t}]} {\n-\t\t\tif {[is_config_true core.bare]\n-\t\t\t\t|| ($_gitworktree eq {}\n-\t\t\t\t\t&& [lindex [file split $_gitdir] end] ne {.git})} {\n-\t\t\t\tset _isbare 1\n-\t\t\t} else {\n-\t\t\t\tset _isbare 0\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn $_isbare\n+\treturn [expr {$::_gitworktree eq {}}]\n }\n \n ######################################################################\n-- \n2.54.0.99.14\n\n"},{"id":"544329","messageId":"20260531230225.126817-10-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 09/12] git-gui: try harder to find worktree from gitdir","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:22Z","receivedAt":"2026-05-31T23:03:08Z","isPatch":true,"body":"git-gui, since 87cd09f43e (\"git-gui: work from the .git dir\",\n2010-01-23), has had the intent to allow starting from inside a\nrepository, then switching to the parent directory if that is a valid\nworktree.\n\nThis certainly hasn't worked since 2d92ab32fd (\"rev-parse: make\n--show-toplevel without a worktree an error\", 2019-11-19) in git, but\nbreaking this git-gui feature was unintentional.\n\nThere are (at least) 3 cases where the gitdir can tell us where the\nworktree is, and we would like all to work:\n\n- core.worktree is set, and points to a valid worktree. This is already\n  handled  by git rev-parse --show-toplevel, even when not in the worktree.\n  There is nothing more to do in this case.\n\n- the gitdir is embedded in a worktree as subdirectory .git. The parent\n  is (or at least should be) a valid worktree. This worked long ago.\n\n- the gitdir is a worktree specific directory (under\n  <mainrepo>/worktrees/worktree_name), within which there is a file\n  \"gitdir\" pointing to .git in the worktree. git gui never learned to\n  handle this case.\n\nLet's handle the latter two cases. Always check that the discovered\nworktree is valid and points to the already discovered gitdir according\nto git rev-parse. This avoids issues that may arise because we are\ndiscovering from the gitdir up, rather than the worktree down, and file\nsystem non-posix behavior or misconfiguration of git might cause\nconfusion.  For instance, a manually moved worktree might not be where\nthe gitdir points, or the gitdir might be configured with\ncore.bare=true.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 38 insertions(+)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex abee222903..f48d929da8 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1103,6 +1103,37 @@ unset argv0dir\n ##\n ## repository setup\n \n+proc find_worktree_from_gitdir {} {\n+\t# this is invoked only if the current directory is inside the repository\n+\tset worktree {}\n+\tif {[file tail $::_gitdir] eq {.git}} {\n+\t\t# the dir containing .git is a worktree if repo allows it\n+\t\t# Check that git reports parent as a worktree (gitdir might not allow a worktree)\n+\t\tif {[catch {\n+\t\t\t\tset parent [file dirname $::_gitdir]\n+\t\t\t\tset worktree [git -C $parent rev-parse --show-toplevel]\n+\t\t\t}]} {\n+\t\t\tset worktree {}\n+\t\t}\n+\t} elseif [file exists {gitdir}] {\n+\t\t# a worktree gitdir has .gitdir naming worktree/.git\n+\t\t# assure git run there reports this dir as the gitdir (links might be broken)\n+\t\tif {[catch {\n+\t\t\t\tset fd_gitdir [open {gitdir} {r}]\n+\t\t\t\tset worktree [file dirname [read $fd_gitdir]]\n+\t\t\t\tcatch {close $fd_gitdir}\n+\t\t\t\tset worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir]\n+\t\t\t\tif {$::_gitdir ne $worktree_gitdir} {\n+\t\t\t\t\tset worktree {}\n+\t\t\t\t}\n+\t\t\t}]} {\n+\t\t\tcatch {close $fd_gitdir}\n+\t\t\tset worktree {}\n+\t\t}\n+\t}\n+\treturn $worktree\n+}\n+\n proc is_gitvars_error {err} {\n \tset havevars 0\n \tset GIT_DIR {}\n@@ -1176,6 +1207,13 @@ if {[catch {\n \tset _prefix {}\n }\n \n+if {[is_bare]} {\n+\t# Maybe we are in an embedded or worktree specific gitdir\n+\tif {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {\n+\t\tset _prefix {}\n+\t}\n+}\n+\n if {![is_bare]} {\n \tif {[catch {cd $_gitworktree} err]} {\n \t\tcatch {wm withdraw .}\n-- \n2.54.0.99.14\n\n"},{"id":"544330","messageId":"20260531230225.126817-11-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 10/12] git-gui: allow specifying path '.' to the browser","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:23Z","receivedAt":"2026-05-31T23:03:08Z","isPatch":true,"body":"Invoking \"git-gui browser rev .\" should show the file browser for the\ncommitish rev, starting at the current directory. When the current\ndirectory is the working tree root, this errors out in normalize_relpath\nbecause the '.' is removed, yielding an empty list as argument to [file\njoin ...]. git ls-tree (underlying the browser) accepts '.', so use that\nas the value when in the root.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex f48d929da8..16d6b3051a 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2993,7 +2993,11 @@ proc normalize_relpath {path} {\n \t\t}\n \t\tlappend elements $item\n \t}\n-\treturn [eval file join $elements]\n+\tif {$elements ne {}} {\n+\t\treturn [eval file join $elements]\n+\t} else {\n+\t\treturn {.}\n+\t}\n }\n \n # -- Not a normal commit type invocation?  Do that instead!\n-- \n2.54.0.99.14\n\n"},{"id":"544331","messageId":"20260531230225.126817-12-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 11/12] git-gui: check browser/blame arguments carefully","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:24Z","receivedAt":"2026-05-31T23:03:09Z","isPatch":true,"body":"git gui offers two related commands, browser and blame, that provide\ngraphical interfaces driven by git ls-tree and git blame. As such, the\narguments to git-gui need to satisfy those two git commands. But,\ngit-gui does not assure this leading to confusing or incorrect results.\nFor instance 'git browser <non-existent path>' shows a blank browser\nwindow rather than error message.\n\nAlso, commit 3e45ee1ef2 (\"git-gui: Smarter command line parsing for\nbrowser, blame\", 2007-05-08) implemented code to allow giving path\nbefore rev on the command line, and unconditionally uses the worktree to\ndisambiguate. As a result, the following command run in a current\ngit-gui checkout of the master branch shows the master branch version of\nblame.tcl, when none should be shown as that file does not exist in\ngitgui-0.6.0.\n\n  git gui blame lib/blame.tcl gitgui-0.6.0\n\nThis 'file before rev' feature in git-gui mirrors ideas considered when\ngit's user interface was very young, but no such feature is documented\nfor any git command.  Rather than try to fix an idea git itself\nrejected, let's just remove this broken and hopefully unused feature.\n\ngit-gui browser|blame both accept 'rev' and 'path' as command line\narguments.  rev defaults to 'HEAD' if not given, while path must be\ngiven. path names a directory tree to ls-tree or a file to blame. path\nmust exist in rev for ls-tree and for blame.  In addition git blame will\ninclude uncommitted changes from the worktree file at 'path' if rev is\nnot given (thus defaulting to HEAD), but still requires that the file\nexists in HEAD.\n\nSo, let's clean up the parser to check that the arguments are usable.\n- give a full synopsis, including '--' that may be used to separate rev and\n  path. (as path is the required final arg, -- gives no extra info)\n- explicitly check the number of arguments\n- use rev-parse to assure a user supplied rev is valid\n- use ls-tree to assure that path exists in rev\n- for blame only, with no rev given and a worktree existing, also assure\n  that path points to a file in the worktree\n\nWith these changes, error messages are thrown by the parser if the path\nor rev are not known: no blank or erroneous displays are created. Also,\nthis avoids accessing the worktree except in the specific use case\nsupported by blame / git-blame, meaning browser|blame now also work\nwithout a worktree.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 122 +++++++++++++++++++++++++++--------------------------\n 1 file changed, 63 insertions(+), 59 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 16d6b3051a..22939215a6 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -3000,101 +3000,105 @@ proc normalize_relpath {path} {\n \t}\n }\n \n+proc show_parse_err {err} {\n+\tif {[tk windowingsystem] eq \"win32\"} {\n+\t\tcatch {wm withdraw .}\n+\t\terror_popup $err\n+\t} else {\n+\t\tputs stderr $err\n+\t}\n+\texit 1\n+}\n+\n # -- Not a normal commit type invocation?  Do that instead!\n #\n switch -- $subcommand {\n browser -\n blame {\n \tif {$subcommand eq \"blame\"} {\n-\t\tset subcommand_args {[--line=<num>] rev? path}\n+\t\tset subcommand_args {[--line=<num>] [rev] [--] <filename>}\n+\t\tset required_pathtype blob\n \t} else {\n-\t\tset subcommand_args {rev? path}\n+\t\tset subcommand_args {[rev] [--] <dirname>}\n+\t\tset required_pathtype tree\n \t}\n-\tif {$argv eq {}} usage\n+\tset maxargs [llength $subcommand_args]\n+\tset nargs [llength $argv]\n+\tif {$nargs < 1 || $nargs > $maxargs} usage\n \tset head {}\n \tset path {}\n \tset jump_spec {}\n-\tset is_path 0\n-\tforeach a $argv {\n-\t\tset p [file join $_prefix $a]\n \n-\t\tif {$is_path || [file exists $p]} {\n-\t\t\tif {$path ne {}} usage\n-\t\t\tset path [normalize_relpath $p]\n-\t\t\tbreak\n+\tset iarg 0\n+\tforeach a $argv {\n+\t\tincr iarg\n+\t\tif {$iarg == $nargs} {\n+\t\t\t# final argument is path\n+\t\t\tset path [normalize_relpath [file join $_prefix $a]]\n \t\t} elseif {$a eq {--}} {\n-\t\t\tif {$path ne {}} {\n-\t\t\t\tif {$head ne {}} usage\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n+\t\t\t# allow before required final arg that must be path\n+\t\t\tif {$iarg != $nargs - 1} {\n+\t\t\t\tusage\n \t\t\t}\n-\t\t\tset is_path 1\n \t\t} elseif {[regexp {^--line=(\\d+)$} $a a lnum]} {\n-\t\t\tif {$jump_spec ne {} || $head ne {}} usage\n+\t\t\t# --line can only be the first arg\n+\t\t\tif {$iarg != 1 || $subcommand ne {blame}} usage\n \t\t\tset jump_spec [list $lnum]\n \t\t} elseif {$head eq {}} {\n-\t\t\tif {$head ne {}} usage\n \t\t\tset head $a\n-\t\t\tset is_path 1\n \t\t} else {\n \t\t\tusage\n \t\t}\n \t}\n-\tunset is_path\n \n-\tif {$head ne {} && $path eq {}} {\n-\t\tif {[string index $head 0] eq {/}} {\n-\t\t\tset path [normalize_relpath $head]\n-\t\t\tset head {}\n+\t# If head not given, use current branch (HEAD),\n+\t# and blame will use worktree if there is one.\n+\tset use_worktree 0\n+\tif {$head eq {}} {\n+\t\tload_current_branch\n+\t\tset head $current_branch\n+\t\tif {$subcommand eq {blame} && ![is_bare]} {\n+\t\t\tif {![file isfile $path]} {\n+\t\t\t\tshow_parse_err [mc \"fatal: no such file '%s' in worktree\" $path]\n+\t\t\t}\n+\t\t\tset use_worktree 1\n+\t\t}\n+\t} else {\n+\t\tif {[catch {\n+\t\t\t\tset commitid \\\n+\t\t\t\t\t[git rev-parse --verify --end-of-options \\\n+\t\t\t\t\t[strcat $head \"^{commit}\"]]\n+\t\t\t}]} {\n+\t\t\tshow_parse_err [mc \"fatal: '%s' is not a valid rev'\" $head]\n \t\t} else {\n-\t\t\tset path [normalize_relpath $_prefix$head]\n-\t\t\tset head {}\n+\t\t\tset current_branch $head\n \t\t}\n \t}\n \n-\tif {$head eq {}} {\n-\t\tload_current_branch\n-\t} else {\n-\t\tif {[regexp [string map \"@@ [expr $hashlength - 1]\" {^[0-9a-f]{1,@@}$}] $head]} {\n-\t\t\tif {[catch {\n-\t\t\t\t\tset head [git rev-parse --verify $head]\n-\t\t\t\t} err]} {\n-\t\t\t\tif {[tk windowingsystem] eq \"win32\"} {\n-\t\t\t\t\ttk_messageBox -icon error -title [mc Error] -message $err\n-\t\t\t\t} else {\n-\t\t\t\t\tputs stderr $err\n-\t\t\t\t}\n-\t\t\t\texit 1\n-\t\t\t}\n+\t# check path is known in head, and is file / directory as required\n+\tset pathtype {}\n+\tcatch {set pathtype [git ls-tree {--format=%(objecttype)} $head $path]}\n+\tif {$pathtype ne {} && $path eq {.}} {\n+\t\t# ls-tree gives contents of root-dir, we need root-dir itself\n+\t\tset pathtype {tree}\n+\t}\n+\n+\tif {$pathtype ne $required_pathtype} {\n+\t\tswitch -- $required_pathtype {\n+\t\t\ttree {show_parse_err \\\n+\t\t\t\t[mc \"'%s' is not a directory in rev '%s'\" $path $head]}\n+\t\t\tblob {show_parse_err \\\n+\t\t\t\t[mc \"'%s' is not a filename in rev '%s'\" $path $head]}\n \t\t}\n-\t\tset current_branch $head\n \t}\n \n \twm deiconify .\n \tswitch -- $subcommand {\n \tbrowser {\n-\t\tif {$jump_spec ne {}} usage\n-\t\tif {$head eq {}} {\n-\t\t\tif {$path ne {} && [file isdirectory $path]} {\n-\t\t\t\tset head $current_branch\n-\t\t\t} else {\n-\t\t\t\tset head $path\n-\t\t\t\tset path {}\n-\t\t\t}\n-\t\t}\n \t\tbrowser::new $head $path\n \t}\n \tblame   {\n-\t\tif {$head eq {} && ![file exists $path]} {\n-\t\t\tcatch {wm withdraw .}\n-\t\t\ttk_messageBox \\\n-\t\t\t\t-icon error \\\n-\t\t\t\t-type ok \\\n-\t\t\t\t-title [mc \"git-gui: fatal error\"] \\\n-\t\t\t\t-message [mc \"fatal: cannot stat path %s: No such file or directory\" $path]\n-\t\t\texit 1\n-\t\t}\n-\t\tblame::new $head $path $jump_spec\n+\t\tblame::new [expr {$use_worktree ? {} : $head}] $path $jump_spec\n \t}\n \t}\n \treturn\n-- \n2.54.0.99.14\n\n"},{"id":"544332","messageId":"20260531230225.126817-13-mlevedahl@gmail.com","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"[PATCH v3 12/12] git-gui: add gui and pick as explicit subcommands","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-05-31T23:02:25Z","receivedAt":"2026-05-31T23:03:11Z","isPatch":true,"body":"git-gui accepts subcommands blame | browser | citool, and assumes the\nsubcommand is 'gui' if none is actually given, But, git-gui also has a\nrepository picker (choose_repository::pick) that can create a new\nrepository + worktree, or choose an existing one, switch to that, and\nthe run the gui. The user has no direct control over invoking the\npicker, instead the picker is triggered by failure in the repository /\nworktree discovery process: this includes being started in a directory\nnot controlled by git, which is probably the intended use case.\n\nThe picker can appear when the user has no intention of creating a new\nworktree, and the user cannot use the picker to create a new worktree\ninside another.\n\nSo, add two explicit subcommands:\n    gui  - Run the gui if repository/worktree discovery succeeds, or die\n           with an error message, but never run the picker.\n    pick - First run the picker, regardless, then start the gui in\n           the chosen worktree.\n\nNothing in this changes the prior behavior, the alternates above must be\nexplicitly selected to see any change.\n\nSigned-off-by: Mark Levedahl <mlevedahl@gmail.com>\n---\n git-gui.sh | 25 +++++++++++++++++++++----\n 1 file changed, 21 insertions(+), 4 deletions(-)\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 22939215a6..933e72c9b2 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -1024,6 +1024,8 @@ proc load_config {include_global} {\n ##\n ## feature option selection\n \n+enable_option picker\n+enable_option gitdir_discovery\n if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {\n \tunset _junk\n } else {\n@@ -1035,6 +1037,9 @@ if {$subcommand eq {gui.sh}} {\n if {$subcommand eq {gui} && [llength $argv] > 0} {\n \tset subcommand [lindex $argv 0]\n \tset argv [lrange $argv 1 end]\n+\tif {$subcommand eq {gui}} {\n+\t\tdisable_option picker\n+\t}\n }\n \n enable_option multicommit\n@@ -1050,6 +1055,7 @@ blame {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tdisable_option picker\n }\n citool {\n \tenable_option singlecommit\n@@ -1058,6 +1064,7 @@ citool {\n \tdisable_option multicommit\n \tdisable_option branch\n \tdisable_option transport\n+\tdisable_option picker\n \n \twhile {[llength $argv] > 0} {\n \t\tset a [lindex $argv 0]\n@@ -1080,6 +1087,9 @@ citool {\n \t\tset argv [lrange $argv 1 end]\n \t}\n }\n+pick {\n+\tdisable_option gitdir_discovery\n+}\n }\n \n ######################################################################\n@@ -1168,7 +1178,7 @@ proc unset_gitdir_vars {} {\n \n # find repository\n set _gitdir {}\n-if {$_gitdir eq {}} {\n+if {[is_enabled gitdir_discovery]} {\n \tif {[catch {\n \t\t\tset _gitdir [git rev-parse --absolute-git-dir]\n \t\t} err]} {\n@@ -1180,7 +1190,7 @@ if {$_gitdir eq {}} {\n }\n \n set picked 0\n-if {$_gitdir eq {}} {\n+if {$_gitdir eq {} && [is_enabled picker]} {\n \tunset_gitdir_vars\n \tload_config 1\n \tapply_config\n@@ -1195,6 +1205,12 @@ if {$_gitdir eq {}} {\n \tset picked 1\n }\n \n+if {$_gitdir eq {}} {\n+\tcatch {wm withdraw .}\n+\terror_popup [strcat [mc \"Git directory not found:\"] \"\\n\\n$err\"]\n+\texit 1\n+}\n+\n # find worktree, continue without if not required\n if {[catch {\n \t\tset _gitworktree [git rev-parse --show-toplevel]\n@@ -3104,14 +3120,15 @@ blame {\n \treturn\n }\n citool -\n-gui {\n+gui -\n+pick {\n \tif {[llength $argv] != 0} {\n \t\tusage\n \t}\n \t# fall through to setup UI for commits\n }\n default {\n-\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool}\\]\"\n+\tset err \"[mc usage:] $argv0 \\[{blame|browser|citool|gui|pick}\\]\"\n \tif {[tk windowingsystem] eq \"win32\"} {\n \t\twm withdraw .\n \t\ttk_messageBox -icon error -message $err \\\n-- \n2.54.0.99.14\n\n"},{"id":"544547","messageId":"8515a482-9a08-4b0a-bd7c-385e1bda1a20@kdbg.org","threadId":"65531","inReplyTo":"20260531230225.126817-1-mlevedahl@gmail.com","subject":"Re: [PATCH v3 00/12] Improve git gui operation without a worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-06-02T17:34:28Z","receivedAt":"2026-06-02T18:18:49Z","isPatch":true,"body":"Am 01.06.26 um 01:02 schrieb Mark Levedahl:\n> git gui has a number of inter-related problems that result in problems\n> during startup from anything but a checked out worktree pointing at a\n> valid git repository. Some of the symptoms are:\n> - blame / browser subcommands, and launching gitk, are intended to be\n>   useful without a worktree, but fail to work.\n> - unlike git, git-gui is supposed to use the parent directory as a\n>   worktree if started from the .git subdirectory in the very common\n>   single worktree + embedded git repository format. This does not\n>   work.\n> - git-gui includes a repository picker allowing a user to select a\n>   worktree from a list and/or start a new repo+worktree: this dialog can\n>   appear at unexpected times, masking useful error feedback on\n>   configuration problems.\n> \n> This patch series addresses the above issues, substantially rewriting\n> the initial repository/worktree process to rely upon git rev-parse so\n> that git's knowledge of access rules, repository configuration, and use\n> of GIT_DIR / GIT_WORK_TREE (or git --gitdir / --work-tree) is used\n> throughout, replacing code largely based upon what git did in 2008. This\n> also means that git gui will naturally gain any new rules implmented in\n> git-core.\n> \n> With this, git-gui only exports GIT_WORK_TREE when non-empty.\n> GIT_WORK_TREE is needed, and must be exported, if the user is overriding\n> core.worktree in the git repository. But, GIT_WORK_TREE cannot be used\n> to specify the lack of a worktree, so exporting an empty GIT_WORK_TREE\n> is one of the problems fixed by this series.\n> \n> v3 of this series addresses j6t's review of v2, with some reordering of\n> patches (1 from j6t added, patch #8 moved to #1), adds another rewrite\n> of the browser / blame parser that eliminates the notion of path before\n> rev on the command line, blame works correctly with a user modified file\n> in the worktree. Clarification is added on the need for GTI_WORK_TREE,\n> and the logic in finding a worktree from the gitdir is simplified.\n> \n> Johannes Sixt (1):\n>   git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk\n> \n> Mark Levedahl (11):\n>   git-gui: use HEAD as current branch when detached\n>   git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE\n>   git-gui: do not change global vars in choose_repository::pick\n>   git-gui: use --absolute-git-dir\n>   git-gui: use rev-parse exclusively to find a repository\n>   git-gui: use git rev-parse for worktree discovery\n>   git-gui: simplify [is_bare] to report if a worktree is known\n>   git-gui: try harder to find worktree from gitdir\n>   git-gui: allow specifying path '.' to the browser\n>   git-gui: check browser/blame arguments carefully\n>   git-gui: add gui and pick as explicit subcommands\n> \n>  git-gui.sh                | 377 ++++++++++++++++++++++----------------\n>  lib/choose_repository.tcl |  21 +--\n>  2 files changed, 223 insertions(+), 175 deletions(-)\n\nThis round looks excellent! Thank you very much!\n\nWhile queuing, I applied the small fixup below to 03/12.\n\n-- Hannes\n\ndiff --git a/git-gui.sh b/git-gui.sh\nindex 933e72c9b255..15dd2b3a84cc 100755\n--- a/git-gui.sh\n+++ b/git-gui.sh\n@@ -2064,7 +2064,6 @@ proc incr_font_size {font {amt 1}} {\n \n proc do_gitk {revs {is_submodule false}} {\n \tglobal current_diff_path file_states current_diff_side ui_index\n-\tglobal _gitworktree\n \n \t# -- Always start gitk through whatever we were loaded with.  This\n \t#    lets us bypass using shell process on Windows systems.\n\n"},{"id":"544552","messageId":"6f4276f9-cf0e-4840-88ce-9e0009c669a9@gmail.com","threadId":"65531","inReplyTo":"8515a482-9a08-4b0a-bd7c-385e1bda1a20@kdbg.org","subject":"Re: [PATCH v3 00/12] Improve git gui operation without a worktree","fromName":"Mark Levedahl","fromEmail":"mlevedahl@gmail.com","sentAt":"2026-06-02T18:54:40Z","receivedAt":"2026-06-02T18:54:42Z","isPatch":true,"body":"\n\nOn 6/2/26 1:34 PM, Johannes Sixt wrote:\n>>  2 files changed, 223 insertions(+), 175 deletions(-)\n> This round looks excellent! Thank you very much!\n>\n> While queuing, I applied the small fixup below to 03/12.\n>\n> -- Hannes\n>\n> diff --git a/git-gui.sh b/git-gui.sh\n> index 933e72c9b255..15dd2b3a84cc 100755\n> --- a/git-gui.sh\n> +++ b/git-gui.sh\n> @@ -2064,7 +2064,6 @@ proc incr_font_size {font {amt 1}} {\n>  \n>  proc do_gitk {revs {is_submodule false}} {\n>  \tglobal current_diff_path file_states current_diff_side ui_index\n> -\tglobal _gitworktree\n>  \n>  \t# -- Always start gitk through whatever we were loaded with.  This\n>  \t#    lets us bypass using shell process on Windows systems.\n>\n\nOops, missed that. Thanks for catching.\n\nYou have duplicate signoffs on 02/12 in your repo.\n\nMark\n"},{"id":"544554","messageId":"9889a979-dfb9-4818-ad11-c4e82a8aacd6@kdbg.org","threadId":"65531","inReplyTo":"6f4276f9-cf0e-4840-88ce-9e0009c669a9@gmail.com","subject":"Re: [PATCH v3 00/12] Improve git gui operation without a worktree","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-06-02T21:05:37Z","receivedAt":"2026-06-02T21:08:40Z","isPatch":true,"body":"Am 02.06.26 um 20:54 schrieb Mark Levedahl:\n> You have duplicate signoffs on 02/12 in your repo.\nThat's intentional. The patch went from my hands to yours, then back to\nmine.\n\n-- Hannes\n\n"}]}