* [PATCH v6 1/3] git-gui: restructure repository startup
From: Shroom Moo @ 2026-05-06 20:27 UTC (permalink / raw)
To: git; +Cc: Johannes Sixt, Mark Levedahl, Shroom Moo
In-Reply-To: <20260506202751.3294-1-egg_mushroomcow@foxmail.com>
When git-gui is started inside a .git directory of a non-bare
repository, it should treat the parent directory as the worktree,
as it did before commit 2d92ab32fd (rev-parse: make --show-toplevel
without a worktree an error, 2019-11-19). However, a bare repository
or a separated gitdir without a worktree must be rejected early.
Protect the previously unguarded calls to `git rev-parse
--show-object-format` and `--show-toplevel`. Restructure the startup
sequence to:
- Check for a bare repository right after loading the config. If the
repository is bare and the current subcommand does not allow bare
repos (e.g. normal commit mode), show "Cannot use bare repository"
and exit.
- When `rev-parse --show-toplevel` fails and the repository is
non-bare, the gitdir path ends with ".git", and we are inside that
gitdir, use the parent directory as the worktree. This preserves
the ability to start git-gui from within a regular repository’s
.git directory, which was intentionally supported since 87cd09f43e56
(git-gui: work from the .git dir, 2010-01-23).
- Otherwise, show a descriptive error and exit.
- Wrap `rev-parse --show-object-format` in a catch to avoid a crash
when the repository configuration is broken (e.g. core.worktree
pointing to an invalid path).
Also removes the old `_prefix`‑based fallback that computed a relative
path to the worktree top from a subdirectory, and the unconditional
`[file dirname $_gitdir]` guess. Both are unnecessary now that
`rev‑parse --show‑toplevel` directly provides the absolute top‑level
path and we can `cd` to it. The guess is further unsafe in
multi‑worktree setups, where a gitdir may have more than one worktree.
The only remaining fallback is the explicit “.git directory” rule for
non‑bare repositories, which mirrors the historical behaviour.
This fixes the fatal Tcl error when the working tree is missing, while
keeping the .git startup feature and avoiding any automatic directory
switching that could be dangerous in multi‑worktree setups.
Helped-by: Johannes Sixt <j6t@kdbg.org>
Helped-by: Mark Levedahl <mlevedahl@gmail.com>
Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>
---
git-gui/git-gui.sh | 72 +++++++++++++++++++++++++++++-----------------
1 file changed, 46 insertions(+), 26 deletions(-)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 23fe76e498..fbdc0b2a41 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1129,7 +1129,8 @@ if {[catch {
}]
&& [catch {
# beware that from the .git dir this sets _gitdir to .
- # and _prefix to the empty string
+ # and _prefix to the empty string; this is handled by
+ # the startup safety checks below
set _gitdir [git rev-parse --git-dir]
set _prefix [git rev-parse --show-prefix]
} err]} {
@@ -1142,8 +1143,20 @@ if {[catch {
set picked 1
}
+if {![file isdirectory $_gitdir]} {
+ catch {wm withdraw .}
+ error_popup [strcat
+ [mc "Git directory not found:"] "\n\n$_gitdir\n\n" \
+ [mc "Please ensure GIT_DIR points to a valid Git repository"]]
+ exit 1
+}
+
# Use object format as hash algorithm (either "sha1" or "sha256")
-set hashalgorithm [git rev-parse --show-object-format]
+if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Failed to determine hash algorithm:"] "\n\n$err"]
+ exit 1
+}
if {$hashalgorithm eq "sha1"} {
set hashlength 40
} elseif {$hashalgorithm eq "sha256"} {
@@ -1160,46 +1173,52 @@ if {$_gitdir eq "."} {
set _gitdir [pwd]
}
-if {![file isdirectory $_gitdir]} {
- catch {wm withdraw .}
- error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
- exit 1
-}
# _gitdir exists, so try loading the config
load_config 0
apply_config
-set _gitworktree [git rev-parse --show-toplevel]
-
-if {$_prefix ne {}} {
- if {$_gitworktree eq {}} {
- regsub -all {[^/]+/} $_prefix ../ cdup
- } else {
- set cdup $_gitworktree
- }
- if {[catch {cd $cdup} err]} {
+# Handle bare repository and determine working tree
+if {[is_bare]} {
+ # Bare repository: only allowed for certain subcommands
+ if {![is_enabled bare]} {
catch {wm withdraw .}
- error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
+ error_popup [strcat [mc "Cannot use bare repository:"] "\n\n" [file normalize $_gitdir]]
exit 1
}
- set _gitworktree [pwd]
- unset cdup
-} elseif {![is_enabled bare]} {
- if {[is_bare]} {
- catch {wm withdraw .}
- error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
- exit 1
+ # Allowed bare repo does not have a worktree
+ set _gitworktree {}
+} else {
+ # Non-bare repository: we must find a worktree
+ if {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {
+ # The only acceptable failure is when we are inside
+ # the .git directory of a regular repository.
+ set inside_gitdir 0
+ catch {set inside_gitdir [git rev-parse --is-inside-git-dir]}
+ if {$inside_gitdir eq {true} && [file tail $_gitdir] eq {.git}} {
+ # Use the parent directory as worktree (historic behavior)
+ set _gitworktree [file normalize [file dirname $_gitdir]]
+ } else {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Cannot determine working tree:"] "\n\n$err"]
+ exit 1
+ }
}
+
if {$_gitworktree eq {}} {
- set _gitworktree [file dirname $_gitdir]
+ catch {wm withdraw .}
+ error_popup [mc "Cannot determine working tree (unexpected empty result)"]
+ exit 1
}
+
if {[catch {cd $_gitworktree} err]} {
catch {wm withdraw .}
- error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
+ error_popup [strcat [mc "Cannot move to working directory:"] "\n\n$err"]
exit 1
}
set _gitworktree [pwd]
}
+
+# Derive a human-readable repository name
set _reponame [file split [file normalize $_gitdir]]
if {[lindex $_reponame end] eq {.git}} {
set _reponame [lindex $_reponame end-1]
@@ -1207,6 +1226,7 @@ if {[lindex $_reponame end] eq {.git}} {
set _reponame [lindex $_reponame end]
}
+# Export the final paths
set env(GIT_DIR) $_gitdir
set env(GIT_WORK_TREE) $_gitworktree
--
2.52.0.windows.1
^ permalink raw reply related
* [PATCH v6 2/3] git-gui: disable gitk visualization when no worktree available
From: Shroom Moo @ 2026-05-06 20:27 UTC (permalink / raw)
To: git; +Cc: Johannes Sixt, Mark Levedahl, Shroom Moo
In-Reply-To: <20260506202751.3294-1-egg_mushroomcow@foxmail.com>
When git-gui is started in a bare repository with the 'bare' option
enabled (e.g., for blame/browser), there is no working tree. The
"Visualize Current Branch's History" and "Visualize All Branch
History" menu items remain enabled, but clicking them triggers a Tcl
error because do_gitk tries to change directory to an empty
_gitworktree.
Fix this by disabling the two visualization menu items when the
repository is bare and the 'bare' option is active. Also update
current_branch_write to keep the state consistent when the branch
changes, and add a defensive check in do_gitk to avoid the error
should the menu state somehow become out of sync.
This complements the startup sequence improvements in the previous
commit, which already correctly identifies bare repositories and
leaves _gitworktree empty in such cases.
Helped-by: Mark Levedahl <mlevedahl@gmail.com>
Helped-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>
---
git-gui/git-gui.sh | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index fbdc0b2a41..1191e6654c 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -2034,6 +2034,10 @@ proc do_gitk {revs {is_submodule false}} {
#
set exe [_which gitk -script]
set cmd [list [info nameofexecutable] $exe]
+ if {$_gitworktree eq {}} {
+ error_popup [mc "Cannot visualize history: no working tree"]
+ return
+ }
if {$exe eq {}} {
error_popup [mc "Couldn't find gitk in PATH"]
} else {
@@ -2657,6 +2661,13 @@ set ui_visualize_current [.mbar.repository index last]
.mbar.repository add command \
-label [mc "Visualize All Branch History"] \
-command {do_gitk --all}
+set ui_visualize_all [.mbar.repository index last]
+
+# Cannot work without a working tree
+if {[is_bare] && [is_enabled bare]} {
+ .mbar.repository entryconf $ui_visualize_current -state disabled
+ .mbar.repository entryconf $ui_visualize_all -state disabled
+}
.mbar.repository add separator
proc current_branch_write {args} {
@@ -2665,6 +2676,13 @@ proc current_branch_write {args} {
-label [mc "Browse %s's Files" $current_branch]
.mbar.repository entryconf $::ui_visualize_current \
-label [mc "Visualize %s's History" $current_branch]
+ if {[is_bare] && [is_enabled bare]} {
+ .mbar.repository entryconf $::ui_visualize_current -state disabled
+ .mbar.repository entryconf $::ui_visualize_all -state disabled
+ } else {
+ .mbar.repository entryconf $::ui_visualize_current -state normal
+ .mbar.repository entryconf $::ui_visualize_all -state normal
+ }
}
trace add variable current_branch write current_branch_write
--
2.52.0.windows.1
^ permalink raw reply related
* [PATCH v6 0/3] git-gui: robustify startup and fix environment handling
From: Shroom Moo @ 2026-05-06 20:27 UTC (permalink / raw)
To: git; +Cc: Johannes Sixt, Mark Levedahl, Shroom Moo
In-Reply-To: <tencent_78B80FB7A0A42E464B3EF1841E2AF3C39509@qq.com>
This series addresses the startup crash introduced by Git commit
"2d92ab32fd ("rev-parse: make --show-toplevel without a worktree an
error", 2019-11-19)", which causes `git gui` to die with a Tcl error
when a worktree is missing (e.g. inside a .git directory without a
working tree, or in a bare repository).
Additionally, it resolves two historically inconsistent behaviours:
- The "Visualize ... History" menu items were enabled in bare
repositories but triggered Tcl errors due to the missing worktree.
- `GIT_WORK_TREE` and `GIT_DIR` environment variables were not
respected early enough, so `GIT_WORK_TREE=/some/path git gui`
often ignored the explicit worktree and brought up the repository
picker, and an exported empty `GIT_WORK_TREE` confused commands
like `git branch --show-current` in bare repositories.
Shroom Moo (3):
git-gui: restructure repository startup
git-gui: disable gitk visualization when no worktree available
git-gui: handle GIT_DIR and GIT_WORK_TREE early
git-gui/git-gui.sh | 170 ++++++++++++++++++++++++++++++++++-----------
1 file changed, 128 insertions(+), 42 deletions(-)
--
2.52.0.windows.1
^ permalink raw reply
* Re: [PATCH v4 6/9] update-ref: handle rejections while adding updates
From: Toon Claes @ 2026-05-06 19:44 UTC (permalink / raw)
To: Karthik Nayak, Patrick Steinhardt; +Cc: git
In-Reply-To: <CAOLa=ZRj11QW16-E6dY2YxDWZ+3moV1h_-S1DfbFPJeOGTjHgg@mail.gmail.com>
Karthik Nayak <karthik.188@gmail.com> writes:
> I'll hold off on a re-roll unless needed.
Well, from my side there aren't any extra comments. I've reviewed the
range-diff and this patch, and glanced through the other patches (but
they didn't change compared to previous version) and all looks good to
me.
--
Cheers,
Toon
^ permalink raw reply
* Re: Git trims the last character of content from remotes
From: René Scharfe @ 2026-05-06 16:00 UTC (permalink / raw)
To: Mikael Magnusson; +Cc: Chris Torek, Hugo Osvaldo Barrera, git
In-Reply-To: <CAHYJk3SW-JwWwk2h=vfDQ4udwQoW2TrmcntiPVwjUJSGiLU2wQ@mail.gmail.com>
On 5/6/26 11:40 AM, Mikael Magnusson wrote:
> On Wed, May 6, 2026 at 11:37 AM Mikael Magnusson <mikachu@gmail.com> wrote:
>>
>> On Tue, May 5, 2026 at 9:46 PM René Scharfe <l.s.r@web.de> wrote:
>>>
>>> On 5/5/26 2:34 AM, Chris Torek wrote:
>>>> On Mon, May 4, 2026 at 10:02 AM Hugo Osvaldo Barrera <hugo@whynothugo.nl> wrote:
>>>> [snippage]
>>>>> When the width of a whole line is the same as my terminal width ...
>>>> [snippage]
>>>>> ... sideband.c prints ANSI_SUFFIX = "\033[K", this escape
>>>>> sequence being "clear the line from the current position until the end of the
>>>>> line", and this is the root cause of the issue.
>>>
>>>> If you have a non-empty prefix
>>>> string before this "clear to end of line" suffix, the solution is more
>>>> obvious: print the ESC [ K as a *prefix* rather than a suffix, but
>>>> that fails with the empty prefix.
>>> We do have a non-empty prefix, but why would it be necessary? What's
>>> wrong with clearing the full line starting from column 1?
>>>
>>> Anyway, do you mean something like this?
>>
>> If the purpose of the clear is to reset the background color on
>> wrapped lines, this will not have any effect, since you clear before
>> the new line is wrapped in. (This is a bit of an obscure edge case, if
>> you set the background color, and wrap the line, the entire new line
>> will be scrolled in with the active background color, then you write
>> perhaps 10 more characters and send the sequence to reset the
>> background color, but the entire rest of the line is still brown, or
>> whatever it was set to when you wrapped).
>>
>> Example command to reproduce locally,
>> % echo -e '\e[43maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\e[0mhihi'
>> (Add more aaaaaaa if necessary so that the line breaks before they end).
>
> Sorry for the double post, but I forgot an important thing, this only
> happens if you *actually* scroll in a new line, ie if you open a new
> terminal and run this, you won't see any problems until you get to the
> bottom of the screen.
The purpose of clearing here is to avoid leaving local progress line
remnants after the remote line. Original discussion:
https://lore.kernel.org/git/alpine.LFD.0.9999.0711032328490.21255@xanadu.home/
You're right that erasing before filling the whole line and then some
is unnecessary. But it wouldn't hurt, either, no?
René
^ permalink raw reply
* Re: Git maintenance fails without meaningful error message if any remote is no longer available
From: Phillip Wood @ 2026-05-06 14:06 UTC (permalink / raw)
To: Anselm Schüler, git; +Cc: Derrick Stolee
In-Reply-To: <0076773f-85c5-475b-96c7-bd85c9e5699a@anselmschueler.com>
Hi Anselm
On 05/05/2026 11:05, Anselm Schüler wrote:
> [ This is a duplicate message because I forgot to hit Reply All the last
> time ]
>
> Hi Phillip,
>
> I think there may be a misunderstanding. My current systemd timers do
> use --keep-going. The issue is that on the individual repo, git-
> maintenance won’t fetch other remotes if one remote fails. --keep-going
> will ensure that the other repos get processed, but the repo with the
> failing remote won’t fetch any remotes after the failing one.
>
> At least, that’s what appears to be happening from the output of the
> command.
Oh sorry I'd misunderstood what was happening. I think that error comes
from "git fetch --all" which dying in connect.c:die_initial_contact().
If there is one remote that is causing problems you could set
remote.<name>.skipFetchAll so that git maintenance does not try and
fetch from it. I've Cc'd Stolee to see if he has any better ideas for
fixing this.
Thanks
Phillip
> On 05/05/2026 11:59, Phillip Wood wrote:
>> Hi Anselm
>>
>> On 30/04/2026 00:13, Anselm Schüler wrote:
>>> I have a repo with multiple remotes, one of which no longer exists.
>>> When git-maintenance runs on it, it fails during the prefetch stage
>>> because that remote doesn’t exist anymore, and gives a mostly
>>> unhelpful error message:
>>>
>>> $ git maintenance run --schedule=daily
>>> ERROR: Repository not found.
>>> fatal: Could not read from remote repository.
>>>
>>> Please make sure you have the correct access rights
>>> and the repository exists.
>>> error: failed to prefetch remotes
>>> error: task 'prefetch' failed
>>>
>>> I think that
>>> 1. git-maintenance should report which remote it’s encountering an
>>> error on
>>> 2. git-maintenance should continue fetching other remotes even if one
>>> fails
>>
>> Since c75662bfc9 (maintenance: running maintenance should not stop on
>> errors, 2024-04-24) which is in git 2.45.3 the systemd timer files
>> installed by "git maintenance start" use "git for-each-repo --keep-
>> going --config=..." to avoid this problem. Unfortunately we don't have
>> a way to automatically upgrade the timer files for users who ran "git
>> maintenance start" before that. I think if you run
>>
>> git maintenance stop
>> git maintenance start
>>
>> It will delete the old timer files and install the new ones. If that
>> does not work you'll need to manually edit the files and add "--keep-
>> going" to "git for-each-repo".
>>
>> Thanks
>>
>> Phillip
>>
>>> Now, on my system, the systemd timers for git-maintenance use git-
>>> for- each-repo. Not sure if that’s upstream behaviour or something
>>> Nix/home- manager does. But if it is upstream behaviour, it would
>>> also be great to report the repo the error comes from, since I
>>> basically had to guess right now which repo was erroring. Luckily I
>>> have only three repos under maintenance so that was fine.
>>>
>>> Let me know if you agree that this should be done. I would be open to
>>> writing a patch (no promises though)
>>>
>>> Anselm
>>>
>>>
>>
>
^ permalink raw reply
* Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir
From: Mark Levedahl @ 2026-05-06 14:05 UTC (permalink / raw)
To: Johannes Sixt; +Cc: git, Shroom Moo
In-Reply-To: <9fecbb11-3cc5-4084-bc29-bd948962dca0@kdbg.org>
On 5/6/26 8:57 AM, Johannes Sixt wrote:
> Am 06.05.26 um 13:27 schrieb Mark Levedahl:
>> A git repository (gitdir) can have config.bare true | false | not set
>> git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the
>> current directory has core.bare==true. This happens whether the call is from inside the
>> gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a
>> symlink or a gitfile link to the gitdir. This call never tells you what directory you are
>> actually in.
> OK. But how does "find out which directory we are in" come into play
> here? If we find a bare repository, we do not need a worktree. If we are
> in a non-bare repository, we can find the worktree with `rev-parse
> --show-toplevel`.
>
>> git rev-parse --is-inside-work-tree gives:
>> true - the call is made from a directory that is suported/supportable as a worktree of
>> a gitdir.
>> false - the call is made from inside a gitdir, or from a directory linked to a to a
>> gitdir with core.bare == true.
>> and error is thrown if no gitdir is discovered.
>>
>> I find --is-inside-work-tree a much better call to make early in setup.
>> true - full git-gui is ok,
>> false - blame/browser is ok (gitdir might have core.bare true)
>> error - no gitdir found, the repository picker should be called.
> But we would still make an exception for the case that $PWD is a
> non-bare repository named ".git", because then, by Git GUI's definition,
> its parent is the corresponding worktree.
I find the organization using rev-parse --is-inside-work-tree easier to reason about, and
if I were writing this from scratch, I would do it that way. But, you have one or more
patches in progress, if this idea is useful there great, otherwise, drop it.
>> As you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty
>> when no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty
>> variable. This cause is in a commit from 12 years ago:
>>
>> 3decb8e0ac ("git-gui: tolerate major version changes when comparing the git version",
>> 2014-05-17)
> I don't think that this commit very relevant. The problem is in `git
> branch --show-current` (and probably other git command variants) that
> want to turn an empty $GIT_WORK_TREE into an absolute path even in cases
> where no worktree is needed. I haven't tried to figure out which commit
> (in the Git repository) started to do this.
Reverting that commit in any way has nothing to do with fixing this problem now. But,
detecting breakage at that commit is what lead me to discover the problem was _gitworktree
== {} and GIT_WORK_TREE="". As you say, browser/blame may well not have broken until a
more recent commit to git itself. I cannot say, even the 2019 git prior to rev parse
--top-level being taught to error out will not build on my computer due to incompatibilities.
>> The fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not
>> export an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for
>> trouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My
>> limited testing shows this works.
> Good to know. My preference is to not set GIT_WORK_TREE at all provided
> that setting GIT_DIR without GIT_WORK_TREE is a use-case supported by Git.
>
>
I just confirmed that git-gui modified to export GIT_DIR only, not GIT_WORK_TREE, and
actually to make sure GIT_WORK_TREE is not in env, has blame/browser working correctly in
a gitdir with no worktree.
Mark
^ permalink raw reply
* Re: [PATCH] doc: add caveat about turning off commit-graph
From: Derrick Stolee @ 2026-05-06 13:59 UTC (permalink / raw)
To: kristofferhaugsbakk, git; +Cc: Kristoffer Haugsbakk
In-Reply-To: <caveat_commit-graph.671@msgid.xyz>
On 5/5/2026 4:45 PM, kristofferhaugsbakk@fastmail.com wrote:
> From: Kristoffer Haugsbakk <code@khaugsbakk.name>
>
> The doc `technical/commit-graph.adoc` says that replace objects and
> commit grafts turn off commit-graph:
>
> Commit grafts and replace objects can change the shape of the commit
> history. The latter can also be enabled/disabled on the fly using
> `--no-replace-objects`. This leads to difficulty storing both possible
> interpretations of a commit id, especially when computing generation
> numbers. The commit-graph will not be read or written when
> replace-objects or grafts are present.
>
> But this isn’t mentioned in the user-facing doc. Let’s mention it on
> git-replace(1) and git-commit-graph(1).
I like your initiative to present this incompatibility in the
user-facing docs.
> +CAVEATS
> +-------
> +
> +The existence of replace objects or commit grafts turns off reading or
> +writing to the commit-graph. See linkgit:git-replace[1].
> +
This does seem a little weak. It doesn't really say how this will
impact the user. Perhaps we could add something about how performance
will likely degrade in this mode?
The existence of replace objects or commit grafts turns off reading or
writing to the commit-graph, which can cause performance issues. See
linkgit:git-replace[1].
Thanks,
-Stolee
^ permalink raw reply
* Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir
From: Johannes Sixt @ 2026-05-06 12:57 UTC (permalink / raw)
To: Mark Levedahl; +Cc: git, Shroom Moo
In-Reply-To: <f6c7c3d5-1d68-45b5-87a7-ae19b59270f4@gmail.com>
Am 06.05.26 um 13:27 schrieb Mark Levedahl:
> A git repository (gitdir) can have config.bare true | false | not set
> git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the
> current directory has core.bare==true. This happens whether the call is from inside the
> gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a
> symlink or a gitfile link to the gitdir. This call never tells you what directory you are
> actually in.
OK. But how does "find out which directory we are in" come into play
here? If we find a bare repository, we do not need a worktree. If we are
in a non-bare repository, we can find the worktree with `rev-parse
--show-toplevel`.
>
> git rev-parse --is-inside-work-tree gives:
> true - the call is made from a directory that is suported/supportable as a worktree of
> a gitdir.
> false - the call is made from inside a gitdir, or from a directory linked to a to a
> gitdir with core.bare == true.
> and error is thrown if no gitdir is discovered.
>
> I find --is-inside-work-tree a much better call to make early in setup.
> true - full git-gui is ok,
> false - blame/browser is ok (gitdir might have core.bare true)
> error - no gitdir found, the repository picker should be called.
But we would still make an exception for the case that $PWD is a
non-bare repository named ".git", because then, by Git GUI's definition,
its parent is the corresponding worktree.
> So, the only need to test if the repo is marked bare is when looking for a possible
> worktree when git-gui was started inside the gitdir, or started in a directory linked to
> said gitdir, or GIT_DIR in the environment points to said gitdir: I consider all of this a
> user (or configuration) error, and there are many possible causes to explore to give
> useful feedback to the user.
How does this scheme work when the user starts `git gui blame` in a bare
repository that does not have a worktree? Would this not produce an
error because no worktree was found?
> As you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty
> when no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty
> variable. This cause is in a commit from 12 years ago:
>
> 3decb8e0ac ("git-gui: tolerate major version changes when comparing the git version",
> 2014-05-17)
I don't think that this commit very relevant. The problem is in `git
branch --show-current` (and probably other git command variants) that
want to turn an empty $GIT_WORK_TREE into an absolute path even in cases
where no worktree is needed. I haven't tried to figure out which commit
(in the Git repository) started to do this.
> The fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not
> export an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for
> trouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My
> limited testing shows this works.
Good to know. My preference is to not set GIT_WORK_TREE at all provided
that setting GIT_DIR without GIT_WORK_TREE is a use-case supported by Git.
-- Hannes
^ permalink raw reply
* Re: [PATCH v6] revision.c: implement --max-count-oldest
From: Mirko Faina @ 2026-05-06 12:54 UTC (permalink / raw)
To: Johannes Sixt
Cc: Junio C Hamano, Jeff King, Jean-Noël Avila,
Patrick Steinhardt, Tian Yuchen, Ben Knoble, Chris Torek, git,
Mirko Faina
In-Reply-To: <7250e6c1-633e-417b-aacb-94e35d240d3f@kdbg.org>
On Wed, May 06, 2026 at 08:45:36AM +0200, Johannes Sixt wrote:
> > +`--max-count-oldest=<number>`::
> > + Just like `--max-count=<number>`, it limits the output to _<number>_
> > + commits. But instead of limiting to the first _<number>_ commits it
> > + limits to the last _<number>_ commits.
> > +
>
> "Just like --max-count" is a surprising addendum in this sentence,
> because the only thing they have in common is the limiting of commits,
> which it repeats anyway. It's more like "Unlike --max-count, limits the
> output to _<number>_ last commits."
Will fix in v7.
> BTW, this makes me think whether this kind of limiting could be
> triggered by a negative argument to --max-count.
Would be a good idea if it weren't for the fact that --max-count < 0 has
for a long time acted like no max count. I'd imagine many could be
asssuming this behaviour in their scripts.
> > `--skip=<number>`::
> > Skip _<number>_ commits before starting to show the commit output.
> >
> > diff --git a/revision.c b/revision.c
> > index 599b3a66c3..3aaa77ced5 100644
> > --- a/revision.c
> > +++ b/revision.c
> > @@ -2339,10 +2339,24 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
> > }
> >
> > if ((argcount = parse_long_opt("max-count", argv, &optarg))) {
> > + if (revs->max_count_type == 1)
> > + die(_("can't use --max-count with --max-count-oldest"));
>
> To help translators, the usual pattern is to say (here and later)
>
> die(_("options '%s' and '%s' cannot be used together"),
> "--max-count", "--max-count-oldest");
Will do.
> > @@ -4521,15 +4535,68 @@ static struct commit *get_revision_internal(struct rev_info *revs)
> > return c;
> > }
> >
> > +static void retrieve_oldest_commits(struct rev_info *revs,
> > + struct commit_list **queue)
> > +{
> > + struct commit *c;
> > + int max_count = revs->max_count;
> > + int queuei_count = 0;
> > + int queueo_count = 0;
> > + struct commit_list *queueo = NULL;
> > + struct commit_list *queuei = NULL;
> > + struct commit_list *reversed_queue = NULL;
> > +
> > + revs->max_count = -1;
> > + while ((c = get_revision_internal(revs))) {
> > + c->object.flags &= ~SHOWN;
> > + commit_list_insert(c, &queuei);
> > + queuei_count++;
> > + while (queuei_count + queueo_count > max_count) {
> > + if (!queueo_count) {
> > + while (queuei_count > 0) {
> > + c = pop_commit(&queuei);
> > + queuei_count--;
> > + commit_list_insert(c, &queueo);
> > + queueo_count++;
> > + }
> > + }
> > + pop_commit(&queueo);
> > + queueo_count--;
> > + }
> > + }
> > +
> > + while ((c = pop_commit(&queueo)))
> > + commit_list_insert(c, &reversed_queue);
> > + while ((c = pop_commit(&queuei)))
> > + commit_list_insert(c, &queueo);
> > + while ((c = pop_commit(&queueo)))
> > + commit_list_insert(c, &reversed_queue);
> > +
> > + while ((c = pop_commit(&reversed_queue)))
> > + commit_list_insert(c, queue);
> > +}
> > +
> > struct commit *get_revision(struct rev_info *revs)
> > {
> > struct commit *c;
> > struct commit_list *reversed;
> > + struct commit_list *queue = NULL;
> > +
> > + if (revs->max_count_type == 1 && !revs->max_count_stage) {
> > + retrieve_oldest_commits(revs, &queue);
> > + commit_list_free(revs->commits);
> > + revs->commits = queue;
> > + revs->max_count_stage = 1;
> > + }
> >
> > if (revs->reverse) {
> > reversed = NULL;
> > - while ((c = get_revision_internal(revs)))
> > - commit_list_insert(c, &reversed);
> > + if (revs->max_count_type == 1)
> > + while ((c = pop_commit(&revs->commits)))
> > + commit_list_insert(c, &reversed);
> > + else
> > + while ((c = get_revision_internal(revs)))
> > + commit_list_insert(c, &reversed);
> > commit_list_free(revs->commits);
> > revs->commits = reversed;
> > revs->reverse = 0;
>
> I would have expected that this kind of commit counting is handled at
> the same spot where --max-count is handled, i.e., in
> get_revision_internal(). It could make a difference in combination with
> sorting options, --boundary, and --graph. The goal is that --max-count
> and --max-count-oldest behave the same in this regard. (But I am in no
> way an expert of the revision walker.)
It doesn't affect sorting options and the graph output by itself is
handled by setting the commits as not shown when we store them, but the
boundary option does break.
Will fix in v7.
Thank you
^ permalink raw reply
* git reflog expire output inconsistency
From: Sven Weiland @ 2026-05-06 12:26 UTC (permalink / raw)
To: git
[-- Attachment #1.1: Type: text/plain, Size: 1397 bytes --]
Hi,
I noticed a small inconsistency in the output of the git reflog expire
command.
Thank you for filling out a Git bug report!
Please answer the following questions to help us understand your issue.
What did you do before the bug happened? (Steps to reproduce your issue)
1. Fill reflog HEAD with something
2. git reflog expire --verbose --expire=now --dry-run HEAD
What did you expect to happen? (Expected behavior)
would prune checkout: <something>
What happened instead? (Actual behavior)
prune checkout: <something>
What's different between what you expected and what actually happened?
It omitted "would ".
Anything else you want to add:
It's just a logging thing the behavior is as expected from the dry-run.
Please review the rest of the bug report below.
You can delete any lines you don't wish to share.
[System Info]
git version:
git version 2.54.0
cpu: x86_64
no commit associated with this build
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
rust: disabled
gettext: enabled
libcurl: 8.5.0
zlib: 1.3
SHA-1: SHA1_DC
SHA-256: SHA256_BLK
default-ref-format: files
default-hash: sha1
uname: Linux 6.17.0-1017-oem #17-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 27
13:48:03 UTC 2026 x86_64
compiler info: gnuc: 13.3
libc info: glibc: 2.39
$SHELL (typically, interactive shell): /usr/bin/zsh
[Enabled Hooks]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 840 bytes --]
^ permalink raw reply
* Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir
From: Mark Levedahl @ 2026-05-06 11:27 UTC (permalink / raw)
To: Johannes Sixt; +Cc: git, Shroom Moo
In-Reply-To: <ac115a8f-5dbc-4988-b8a5-c1647af1bb74@kdbg.org>
On 5/6/26 3:32 AM, Johannes Sixt wrote:
>> Whether being in a gitdir is ok, or a worktree required, is of fundamental importance and
>> is not explicitly checked now. This is my issue. (Whether the repo is bare, or embedded in
>> a worktree, is relevant only when automatically fixing a user error.)
> I don't quite follow what you a trying to say here.
I played a bit more:
A git repository (gitdir) can have config.bare true | false | not set
git rev-parse --is-bare-repository tells you that whatever gitdir is discovered from the
current directory has core.bare==true. This happens whether the call is from inside the
gitdir, or in the parent dir of a gitdir named '.git', or in a directory containing a
symlink or a gitfile link to the gitdir. This call never tells you what directory you are
actually in.
git rev-parse --is-inside-work-tree gives:
true - the call is made from a directory that is suported/supportable as a worktree of
a gitdir.
false - the call is made from inside a gitdir, or from a directory linked to a to a
gitdir with core.bare == true.
and error is thrown if no gitdir is discovered.
I find --is-inside-work-tree a much better call to make early in setup.
true - full git-gui is ok,
false - blame/browser is ok (gitdir might have core.bare true)
error - no gitdir found, the repository picker should be called.
So, the only need to test if the repo is marked bare is when looking for a possible
worktree when git-gui was started inside the gitdir, or started in a directory linked to
said gitdir, or GIT_DIR in the environment points to said gitdir: I consider all of this a
user (or configuration) error, and there are many possible causes to explore to give
useful feedback to the user.
But, there are many ways to code this. I started down a path of using
--is-inside-worktree, but in the end there are still a lot of corner cases to find.
>>> But perhaps there is a simpler solution: Let's present an error if
>>> --show-toplevel fails except in the case where the startup directory is
>>> named '.git' (and is a valid Git repository) and is not bare (then the
>>> worktree is the parent). I insist in this exception, because this
>>> use-case was considered important in the past (87cd09f43e56 "git-gui:
>>> work from the .git dir", 2010-01-23).
>>>
>>> -- Hannes
>>>
>> This would not fix gitk's blame / browse from a gitdir, and I don't really see a one or
>> two line fix as being adequate.
As you mentioned elsewhere, the problem on browser/blame is that _gitworktree is empty
when no worktree is found, so GIT_WORK_TREE is exported to the environment as an empty
variable. This cause is in a commit from 12 years ago:
3decb8e0ac ("git-gui: tolerate major version changes when comparing the git version",
2014-05-17)
Prior to that commit and if not using git v1.7.x, an alternate branch of code not using
git rev-parse was used for worktree discovery, and that code set _gitworktree = _gitdir
when in a gitdir. The alternate code was removed more recently as it was unreachable from
non-ancient git versions.
The fix is to set _gitworktree to _gitdir before exporting GIT_WORK_TREE, or to just not
export an empty GIT_WORK_TREE. Obviously, having GIT_WORK_TREE = GIT_DIR is asking for
trouble, but perhaps is ok as git-gui is running in a read-only mode for browse/blame. My
limited testing shows this works.
Mark
^ permalink raw reply
* [PATCH] doc: fix typos via codespell
From: Andrew Kreimer @ 2026-05-06 10:15 UTC (permalink / raw)
To: git; +Cc: Andrew Kreimer
There are some typos in the documentation, comments, etc.
Fix them via codespell.
Signed-off-by: Andrew Kreimer <algonell@gmail.com>
---
Documentation/SubmittingPatches | 2 +-
Documentation/git-sparse-checkout.adoc | 2 +-
Documentation/technical/build-systems.adoc | 6 +++---
builtin/pack-objects.c | 2 +-
commit-graph.h | 2 +-
compat/precompose_utf8.c | 2 +-
git-gui/git-gui.sh | 2 +-
git-gui/lib/choose_repository.tcl | 2 +-
git-gui/lib/themed.tcl | 2 +-
hook.h | 2 +-
meson_options.txt | 2 +-
midx-write.c | 4 ++--
odb/source.h | 2 +-
packfile.h | 2 +-
path.h | 2 +-
po/el.po | 2 +-
po/ko.po | 2 +-
reftable/system.h | 2 +-
t/README | 2 +-
t/chainlint.pl | 2 +-
t/chainlint/chain-break-false.expect | 2 +-
t/chainlint/chain-break-false.test | 2 +-
t/t1700-split-index.sh | 2 +-
t/t3909-stash-pathspec-file.sh | 6 +++---
t/t4052-stat-output.sh | 2 +-
t/t4067-diff-partial-clone.sh | 2 +-
t/t9150/svk-merge.dump | 10 +++++-----
t/t9151/svn-mergeinfo.dump | 18 +++++++++---------
t/unit-tests/clar/README.md | 2 +-
29 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index d570184ec8..35b4952c8a 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -92,7 +92,7 @@ input and avoids unnecessary churn from many rapid iterations.
topic are appropriate, so such an incremental updates are limited to
small corrections and polishing. After a topic cooks for some time
(like 7 calendar days) in 'next' without needing further tweaks on
- top, it gets merged to the 'master' branch and wait to become part
+ top, it gets merged to the 'master' branch and waits to become part
of the next major release.
In the following sections, many techniques and conventions are listed
diff --git a/Documentation/git-sparse-checkout.adoc b/Documentation/git-sparse-checkout.adoc
index 0d1618f161..e286584c67 100644
--- a/Documentation/git-sparse-checkout.adoc
+++ b/Documentation/git-sparse-checkout.adoc
@@ -134,7 +134,7 @@ the `clean.requireForce` config option is set to `false`.
+
The `--dry-run` option will list the directories that would be removed
without deleting them. Running in this mode can be helpful to predict the
-behavior of the clean comand or to determine which kinds of files are left
+behavior of the clean command or to determine which kinds of files are left
in the sparse directories.
+
The `--verbose` option will list every file within the directories that
diff --git a/Documentation/technical/build-systems.adoc b/Documentation/technical/build-systems.adoc
index 3c5237b9fd..ca5b5d96f1 100644
--- a/Documentation/technical/build-systems.adoc
+++ b/Documentation/technical/build-systems.adoc
@@ -47,7 +47,7 @@ Auto-detection of the following items is considered to be important:
- Check for the existence of headers.
- Check for the existence of libraries.
- - Check for the existence of exectuables.
+ - Check for the existence of executables.
- Check for the runtime behavior of specific functions.
- Check for specific link order requirements when multiple libraries are
involved.
@@ -106,7 +106,7 @@ by the build system:
- C: the primary compiled language used by Git, must be supported. Relevant
toolchains are GCC, Clang and MSVC.
- - Rust: candidate as a second compiled lanugage, should be supported. Relevant
+ - Rust: candidate as a second compiled language, should be supported. Relevant
toolchains is the LLVM-based rustc.
Built-in support for the respective languages is preferred over support that
@@ -142,7 +142,7 @@ The following list of build systems are considered:
=== GNU Make
-- Platform support: ubitquitous on all platforms, but not well-integrated into Windows.
+- Platform support: ubiquitous on all platforms, but not well-integrated into Windows.
- Auto-detection: no built-in support for auto-detection of features.
- Ease of use: easy to use, but discovering available options is hard. Makefile
rules can quickly get out of hand once reaching a certain scope.
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index dd2480a73d..806068907e 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1341,7 +1341,7 @@ static void write_pack_file(void)
* length of them as buffer length.
*
* Note that we need to subtract one though to
- * accomodate for the sideband byte.
+ * accommodate for the sideband byte.
*/
struct hashfd_options opts = {
.progress = progress_state,
diff --git a/commit-graph.h b/commit-graph.h
index f6a5433641..13ca4ff010 100644
--- a/commit-graph.h
+++ b/commit-graph.h
@@ -18,7 +18,7 @@
* This method is only used to enhance coverage of the commit-graph
* feature in the test suite with the GIT_TEST_COMMIT_GRAPH and
* GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS environment variables. Do not
- * call this method oustide of a builtin, and only if you know what
+ * call this method outside of a builtin, and only if you know what
* you are doing!
*/
void git_test_write_commit_graph_or_die(struct odb_source *source);
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index 43b3be0114..6e709bd138 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -85,7 +85,7 @@ const char *precompose_string_if_needed(const char *in)
out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen);
if (out) {
if (outlen == inlen && !memcmp(in, out, outlen))
- free(out); /* no need to return indentical */
+ free(out); /* no need to return identical */
else
in = out;
}
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 23fe76e498..40e95bccb4 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -109,7 +109,7 @@ foreach p [split $env(PATH) $_path_sep] {
if {[file pathtype $p] ne {absolute}} {
continue
}
- # Keep only the first occurence of any duplicates.
+ # Keep only the first occurrence of any duplicates.
set norm_p [file normalize $p]
dict set _path_seen $norm_p 1
}
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
index 7e1462a20c..a4703af028 100644
--- a/git-gui/lib/choose_repository.tcl
+++ b/git-gui/lib/choose_repository.tcl
@@ -15,7 +15,7 @@ field w_recentlist ; # Listbox containing recent repositories
field w_localpath ; # Entry widget bound to local_path
field done 0 ; # Finished picking the repository?
-field clone_ok false ; # clone succeeeded
+field clone_ok false ; # clone succeeded
field local_path {} ; # Where this repository is locally
field origin_url {} ; # Where we are cloning from
field origin_name origin ; # What we shall call 'origin'
diff --git a/git-gui/lib/themed.tcl b/git-gui/lib/themed.tcl
index c18e201d85..f4cffeac66 100644
--- a/git-gui/lib/themed.tcl
+++ b/git-gui/lib/themed.tcl
@@ -4,7 +4,7 @@
namespace eval color {
# Variable colors
- # Preffered way to set widget colors is using add_option.
+ # Preferred way to set widget colors is using add_option.
# In some cases, like with tags in_diff/in_sel, we use these colors.
variable select_bg lightgray
variable select_fg black
diff --git a/hook.h b/hook.h
index 5c5628dd1f..5f0c3f19bb 100644
--- a/hook.h
+++ b/hook.h
@@ -116,7 +116,7 @@ struct run_hooks_opt {
* While the callback allows piecemeal writing, it can also be
* used for smaller inputs, where it gets called only once.
*
- * Add hook callback initalization context to `feed_pipe_ctx`.
+ * Add hook callback initialization context to `feed_pipe_ctx`.
* Add hook callback internal state to `feed_pipe_cb_data`.
*
*/
diff --git a/meson_options.txt b/meson_options.txt
index 659cbb218f..1ed228d42a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -106,7 +106,7 @@ option('highlight_bin', type: 'string', value: 'highlight')
# Documentation.
option('docs', type: 'array', choices: ['man', 'html'], value: [],
- description: 'Which documenattion formats to build and install.')
+ description: 'Which documentation formats to build and install.')
option('default_help_format', type: 'combo', choices: ['man', 'html', 'platform'], value: 'platform',
description: 'Default format used when executing git-help(1).')
option('docs_backend', type: 'combo', choices: ['asciidoc', 'asciidoctor', 'auto'], value: 'auto',
diff --git a/midx-write.c b/midx-write.c
index a25cab75ab..6d6d29c6cd 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1152,7 +1152,7 @@ static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_c
/*
* Ensure that we have a valid checksum before consulting the
- * exisiting MIDX in order to determine if we can avoid an
+ * existing MIDX in order to determine if we can avoid an
* update.
*
* This is necessary because the given MIDX is loaded directly
@@ -1438,7 +1438,7 @@ static int write_midx_internal(struct write_midx_opts *opts)
/*
* Attempt opening the pack index to populate num_objects.
- * Ignore failiures as they can be expected and are not
+ * Ignore failures as they can be expected and are not
* fatal during this selection time.
*/
open_pack_index(oldest);
diff --git a/odb/source.h b/odb/source.h
index f706e0608a..4958a503cf 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -338,7 +338,7 @@ static inline int odb_source_read_object_stream(struct odb_read_stream **out,
* are only iterated over once.
*
* The optional `request` structure serves as a template for retrieving the
- * object info for each indvidual iterated object and will be populated as if
+ * object info for each individual iterated object and will be populated as if
* `odb_source_read_object_info()` was called on the object. It will not be
* modified, the callback will instead be invoked with a separate `struct
* object_info` for every object. Object info will not be read when passing a
diff --git a/packfile.h b/packfile.h
index 9b647da7dd..6dea707ba4 100644
--- a/packfile.h
+++ b/packfile.h
@@ -124,7 +124,7 @@ struct packfile_store {
* that packs that contain a lot of accessed objects will be located
* towards the front.
*
- * This is usually desireable, but there are exceptions. One exception
+ * This is usually desirable, but there are exceptions. One exception
* is when the looking up multiple objects in a loop for each packfile.
* In that case, we may easily end up with an infinite loop as the
* packfiles get reordered to the front repeatedly.
diff --git a/path.h b/path.h
index 0434ba5e07..4c2958a903 100644
--- a/path.h
+++ b/path.h
@@ -217,7 +217,7 @@ void safe_create_dir(struct repository *repo, const char *dir, int share);
*
* - It always adjusts shared permissions.
*
- * Returns a negative erorr code on error, 0 on success.
+ * Returns a negative error code on error, 0 on success.
*/
int safe_create_dir_in_gitdir(struct repository *repo, const char *path);
diff --git a/po/el.po b/po/el.po
index 703f46d0c7..c45560c996 100644
--- a/po/el.po
+++ b/po/el.po
@@ -2748,7 +2748,7 @@ msgid "Low-level Commands / Interrogators"
msgstr "Εντολές Χαμηλού Επιπέδου / Ερωτημάτων"
#: help.c:37
-msgid "Low-level Commands / Synching Repositories"
+msgid "Low-level Commands / Syncing Repositories"
msgstr "Εντολές Χαμηλού Επιπέδου / Συγχρονισμού Αποθετηρίων"
#: help.c:38
diff --git a/po/ko.po b/po/ko.po
index 7a6847f023..6bc20a43e3 100644
--- a/po/ko.po
+++ b/po/ko.po
@@ -2062,7 +2062,7 @@ msgid "Low-level Commands / Interrogators"
msgstr "보조 명령 / 정보 획득 기능"
#: help.c:37
-msgid "Low-level Commands / Synching Repositories"
+msgid "Low-level Commands / Syncing Repositories"
msgstr "보조 명령 / 저장소 동기화 기능"
#: help.c:38
diff --git a/reftable/system.h b/reftable/system.h
index c0e2cbe0ff..628232a46f 100644
--- a/reftable/system.h
+++ b/reftable/system.h
@@ -84,7 +84,7 @@ struct reftable_flock {
* to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative
* we block indefinitely.
*
- * Retrun 0 on success, a reftable error code on error. Specifically,
+ * Return 0 on success, a reftable error code on error. Specifically,
* `REFTABLE_LOCK_ERROR` should be returned in case the target path is already
* locked.
*/
diff --git a/t/README b/t/README
index adbbd9acf4..085921be4b 100644
--- a/t/README
+++ b/t/README
@@ -972,7 +972,7 @@ see test-lib-functions.sh for the full list and their options.
- test_lazy_prereq <prereq> <script>
Declare the way to determine if a test prerequisite <prereq> is
- satisified or not, but delay the actual determination until the
+ satisfied or not, but delay the actual determination until the
prerequisite is actually used by "test_have_prereq" or the
three-arg form of the test_expect_* functions. For example, this
is how the SYMLINKS prerequisite is declared to see if the platform
diff --git a/t/chainlint.pl b/t/chainlint.pl
index f0598e3934..2d07a99700 100755
--- a/t/chainlint.pl
+++ b/t/chainlint.pl
@@ -35,7 +35,7 @@
#
# In other languages, `1+2` would typically be scanned as three tokens
# (`1`, `+`, and `2`), but in shell it is a single token. However, the similar
-# `1 + 2`, which embeds whitepace, is scanned as three token in shell, as well.
+# `1 + 2`, which embeds whitespace, is scanned as three token in shell, as well.
# In shell, several characters with special meaning lose that meaning when not
# surrounded by whitespace. For instance, the negation operator `!` is special
# when standing alone surrounded by whitespace; whereas in `foo!uucp` it is
diff --git a/t/chainlint/chain-break-false.expect b/t/chainlint/chain-break-false.expect
index f6a0a301e9..db6f8b12a4 100644
--- a/t/chainlint/chain-break-false.expect
+++ b/t/chainlint/chain-break-false.expect
@@ -1,4 +1,4 @@
-2 if condition not satisified
+2 if condition not satisfied
3 then
4 echo it did not work...
5 echo failed!
diff --git a/t/chainlint/chain-break-false.test b/t/chainlint/chain-break-false.test
index f78ad911fc..924c9627c0 100644
--- a/t/chainlint/chain-break-false.test
+++ b/t/chainlint/chain-break-false.test
@@ -1,6 +1,6 @@
test_expect_success 'chain-break-false' '
# LINT: broken &&-chain okay if explicit "false" signals failure
-if condition not satisified
+if condition not satisfied
then
echo it did not work...
echo failed!
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index ac4a5b2734..869fb4a14e 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -502,7 +502,7 @@ test_expect_success 'do not refresh null base index' '
git checkout main &&
git update-index --split-index &&
test_commit more &&
- # must not write a new shareindex, or we wont catch the problem
+ # must not write a new shareindex, or we won't catch the problem
git -c splitIndex.maxPercentChange=100 merge --no-edit side-branch 2>err &&
# i.e. do not expect warnings like
# could not freshen shared index .../shareindex.00000...
diff --git a/t/t3909-stash-pathspec-file.sh b/t/t3909-stash-pathspec-file.sh
index 73f2dbdeb0..3afa6bff3d 100755
--- a/t/t3909-stash-pathspec-file.sh
+++ b/t/t3909-stash-pathspec-file.sh
@@ -29,7 +29,7 @@ verify_expect () {
test_expect_success 'simplest' '
restore_checkpoint &&
- # More files are written to make sure that git didnt ignore
+ # More files are written to make sure that git didn't ignore
# --pathspec-from-file, stashing everything
echo A >fileA.t &&
echo B >fileB.t &&
@@ -47,7 +47,7 @@ test_expect_success 'simplest' '
test_expect_success '--pathspec-file-nul' '
restore_checkpoint &&
- # More files are written to make sure that git didnt ignore
+ # More files are written to make sure that git didn't ignore
# --pathspec-from-file, stashing everything
echo A >fileA.t &&
echo B >fileB.t &&
@@ -66,7 +66,7 @@ test_expect_success '--pathspec-file-nul' '
test_expect_success 'only touches what was listed' '
restore_checkpoint &&
- # More files are written to make sure that git didnt ignore
+ # More files are written to make sure that git didn't ignore
# --pathspec-from-file, stashing everything
echo A >fileA.t &&
echo B >fileB.t &&
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh
index 7c749062e2..df4999b326 100755
--- a/t/t4052-stat-output.sh
+++ b/t/t4052-stat-output.sh
@@ -420,7 +420,7 @@ test_expect_success 'merge --stat respects COLUMNS with long name' '
# enough terminal display width, will contain the following line:
# "<RED>|<RESET> ${FILENAME} | 0"
# where "<RED>" and "<RESET>" are ANSI escape codes to color the text.
-# To calculate the minimium terminal display width MIN_TERM_WIDTH so that the
+# To calculate the minimum terminal display width MIN_TERM_WIDTH so that the
# FILENAME in the diffstat will not be shortened, we take the FILENAME length
# and add 9 to it.
# To check if the diffstat width, when the line_prefix (the "<RED>|<RESET>" of
diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh
index 30813109ac..a9dec84c30 100755
--- a/t/t4067-diff-partial-clone.sh
+++ b/t/t4067-diff-partial-clone.sh
@@ -159,7 +159,7 @@ test_expect_success 'diff succeeds even if prefetch triggered by break-rewrites'
# We need baz to trigger break-rewrites detection.
git -C client reset --hard HEAD &&
- # break-rewrites detction in reset.
+ # break-rewrites detection in reset.
git -C client reset HEAD~1
'
diff --git a/t/t9150/svk-merge.dump b/t/t9150/svk-merge.dump
index 42f70dbec7..6a8ac81b11 100644
--- a/t/t9150/svk-merge.dump
+++ b/t/t9150/svk-merge.dump
@@ -77,7 +77,7 @@ Content-length: 2411
PROPS-END
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -206,7 +206,7 @@ Content-length: 2465
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -310,7 +310,7 @@ Content-length: 2521
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -417,7 +417,7 @@ Content-length: 2593
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -534,7 +534,7 @@ Content-length: 2713
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
diff --git a/t/t9151/svn-mergeinfo.dump b/t/t9151/svn-mergeinfo.dump
index 47cafcf528..d5e1695637 100644
--- a/t/t9151/svn-mergeinfo.dump
+++ b/t/t9151/svn-mergeinfo.dump
@@ -87,7 +87,7 @@ Content-length: 2411
PROPS-END
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -260,7 +260,7 @@ Content-length: 2465
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -365,7 +365,7 @@ Content-length: 2521
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -473,7 +473,7 @@ Content-length: 2529
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -578,7 +578,7 @@ Content-length: 2593
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -767,7 +767,7 @@ Content-length: 2593
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -948,7 +948,7 @@ Content-length: 2713
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -1172,7 +1172,7 @@ Content-length: 2713
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
@@ -1414,7 +1414,7 @@ Content-length: 2713
# -DCOLLISION_CHECK if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# enough guarantees about no collisions between objects ever hapenning.
+# enough guarantees about no collisions between objects ever happening.
#
# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
# Note that you need some new glibc (at least >2.2.4) for this, and it will
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
index 41595989ca..a45b9c8e5d 100644
--- a/t/unit-tests/clar/README.md
+++ b/t/unit-tests/clar/README.md
@@ -138,7 +138,7 @@ raise errors during test execution.
__Caution:__ If you use assertions inside of `test_suitename__initialize`,
make sure that you do not rely on `__initialize` being completely run
inside your `test_suitename__cleanup` function. Otherwise you might
-encounter ressource cleanup twice.
+encounter resource cleanup twice.
## How does Clar work?
--
2.54.0
^ permalink raw reply related
* Re: Git trims the last character of content from remotes
From: Mikael Magnusson @ 2026-05-06 9:40 UTC (permalink / raw)
To: René Scharfe; +Cc: Chris Torek, Hugo Osvaldo Barrera, git
In-Reply-To: <CAHYJk3Q6xjW8mBvbQkN3vsDb2e9Em6PuDinFoTFwqkTXaKK=rQ@mail.gmail.com>
On Wed, May 6, 2026 at 11:37 AM Mikael Magnusson <mikachu@gmail.com> wrote:
>
> On Tue, May 5, 2026 at 9:46 PM René Scharfe <l.s.r@web.de> wrote:
> >
> > On 5/5/26 2:34 AM, Chris Torek wrote:
> > > On Mon, May 4, 2026 at 10:02 AM Hugo Osvaldo Barrera <hugo@whynothugo.nl> wrote:
> > > [snippage]
> > >> When the width of a whole line is the same as my terminal width ...
> > > [snippage]
> > >> ... sideband.c prints ANSI_SUFFIX = "\033[K", this escape
> > >> sequence being "clear the line from the current position until the end of the
> > >> line", and this is the root cause of the issue.
> >
> > > If you have a non-empty prefix
> > > string before this "clear to end of line" suffix, the solution is more
> > > obvious: print the ESC [ K as a *prefix* rather than a suffix, but
> > > that fails with the empty prefix.
> > We do have a non-empty prefix, but why would it be necessary? What's
> > wrong with clearing the full line starting from column 1?
> >
> > Anyway, do you mean something like this?
>
> If the purpose of the clear is to reset the background color on
> wrapped lines, this will not have any effect, since you clear before
> the new line is wrapped in. (This is a bit of an obscure edge case, if
> you set the background color, and wrap the line, the entire new line
> will be scrolled in with the active background color, then you write
> perhaps 10 more characters and send the sequence to reset the
> background color, but the entire rest of the line is still brown, or
> whatever it was set to when you wrapped).
>
> Example command to reproduce locally,
> % echo -e '\e[43maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\e[0mhihi'
> (Add more aaaaaaa if necessary so that the line breaks before they end).
Sorry for the double post, but I forgot an important thing, this only
happens if you *actually* scroll in a new line, ie if you open a new
terminal and run this, you won't see any problems until you get to the
bottom of the screen.
--
Mikael Magnusson
^ permalink raw reply
* Re: Git trims the last character of content from remotes
From: Mikael Magnusson @ 2026-05-06 9:37 UTC (permalink / raw)
To: René Scharfe; +Cc: Chris Torek, Hugo Osvaldo Barrera, git
In-Reply-To: <3364c573-b7f4-4ec0-b471-312aa11028fe@web.de>
On Tue, May 5, 2026 at 9:46 PM René Scharfe <l.s.r@web.de> wrote:
>
> On 5/5/26 2:34 AM, Chris Torek wrote:
> > On Mon, May 4, 2026 at 10:02 AM Hugo Osvaldo Barrera <hugo@whynothugo.nl> wrote:
> > [snippage]
> >> When the width of a whole line is the same as my terminal width ...
> > [snippage]
> >> ... sideband.c prints ANSI_SUFFIX = "\033[K", this escape
> >> sequence being "clear the line from the current position until the end of the
> >> line", and this is the root cause of the issue.
>
> > If you have a non-empty prefix
> > string before this "clear to end of line" suffix, the solution is more
> > obvious: print the ESC [ K as a *prefix* rather than a suffix, but
> > that fails with the empty prefix.
> We do have a non-empty prefix, but why would it be necessary? What's
> wrong with clearing the full line starting from column 1?
>
> Anyway, do you mean something like this?
If the purpose of the clear is to reset the background color on
wrapped lines, this will not have any effect, since you clear before
the new line is wrapped in. (This is a bit of an obscure edge case, if
you set the background color, and wrap the line, the entire new line
will be scrolled in with the active background color, then you write
perhaps 10 more characters and send the sequence to reset the
background color, but the entire rest of the line is still brown, or
whatever it was set to when you wrapped).
Example command to reproduce locally,
% echo -e '\e[43maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\e[0mhihi'
(Add more aaaaaaa if necessary so that the line breaks before they end).
--
Mikael Magnusson
^ permalink raw reply
* Re: [PATCH v2 11/11] ci: run expensive tests on push builds to integration branches
From: Johannes Schindelin @ 2026-05-06 8:33 UTC (permalink / raw)
To: Junio C Hamano
Cc: Derrick Stolee, Johannes Schindelin via GitGitGadget, git,
Torsten Bögershausen, Jeff King, Patrick Steinhardt
In-Reply-To: <CAPc5daUzr+mn6ojzsqpW6mCXzc2yVqpevVk8njefx4j09G_OgA@mail.gmail.com>
Hi Junio,
On Wed, 6 May 2026, Junio C Hamano wrote:
> https://github.com/git/git/actions/runs/25366120610/job/74377320625
>
> We seem to be hitting the same _Generic error in various (but not all) jobs
>
> /usr/include/x86_64-linux-gnu/sys/cdefs.h:838:3: note: expanded from
> macro '__glibc_const_generic'
> 838 | _Generic (0 ? (PTR) : (void *) 1, \
> | ^
> Error: list-objects-filter-options.c:222:10: '_Generic' is a C11
> extension [-Werror,-Wc11-extensions]
>
> I thought we updated the codebase to avoid stripping away constness
> with strchr() and friends, but the error seems to be more like one
> hand in the system passing -Wc11-extensions to stick to older version
> of C and the other hand in the system that uses _Generic to implement
> the const/non-const variants of strchr() in the system header not
> knowing that the other tells C11 const-preserving strchr() should not
> be used?
This was diagnosed (with a proposed fix) by Patrick over in
https://lore.kernel.org/git/20260505-b4-pks-ci-tolerate-glibc-generic-v1-1-5786386fe512@pks.im/.
tl;dr It's not about `const`-ness at all, but about glibc using a C11
construct which clang's strict c99 checker now refuses, thanks to the
upgrade to Ubuntu 26.04 in the `ubuntu:rolling` runners.
Ciao,
Johannes
^ permalink raw reply
* Re: [PATCH v3 1/1] git-gui: handle missing worktree and separated gitdir
From: Johannes Sixt @ 2026-05-06 7:32 UTC (permalink / raw)
To: Mark Levedahl; +Cc: git, Shroom Moo
In-Reply-To: <7d5cf952-badb-4071-a0eb-af9443fa8b5b@gmail.com>
Am 04.05.26 um 17:13 schrieb Mark Levedahl:
> On 5/3/26 4:53 AM, Johannes Sixt wrote:
>> I would not call the use of --is-bar-repository instead of
>> --is-inside-git-dir an error, just a choice that has been made. In
>> particular, when the startup directory is named '.git' and is not marked
>> as bare, then its parent directory can very reasonably be taken as its
>> worktree. (That's how things worked before --show-toplevel was used.) If
>> the check is for --is-inside-git-dir, this treatment would be ruled out
>> early.
>>
> Whether being in a gitdir is ok, or a worktree required, is of fundamental importance and
> is not explicitly checked now. This is my issue. (Whether the repo is bare, or embedded in
> a worktree, is relevant only when automatically fixing a user error.)
I don't quite follow what you a trying to say here.
>> But perhaps there is a simpler solution: Let's present an error if
>> --show-toplevel fails except in the case where the startup directory is
>> named '.git' (and is a valid Git repository) and is not bare (then the
>> worktree is the parent). I insist in this exception, because this
>> use-case was considered important in the past (87cd09f43e56 "git-gui:
>> work from the .git dir", 2010-01-23).
>>
>> -- Hannes
>>
>
> This would not fix gitk's blame / browse from a gitdir, and I don't really see a one or
> two line fix as being adequate.
>
> git-gui sets GIT_WORK_TREE and GIT_DIR at startup. GIT_DIR passes my simple tests, but
> mishandles GIT_WORK_TREE.
>
> I expect these two invocations to be equivalent, both starting git-gui in the worktree
> '/some/path':
>
> GIT_WORK_TREE=/some/path git gui
> git -C /some/path gui
>
> But, the GIT_WORK_TREE approach:
> works as I expect ONLY when the current directory is a valid worktree
> when started from a gitdir, uses that gitdir in conjunction with the requested worktree
> when started from an uncontrolled directory, shows the repository picker.
The important aspect here isn't about the worktree, but whether a gitdir
can be determined for the current directory. All three observations make
total sense.
That said, setting GIT_WORK_TREE without also setting GIT_DIR is
undefined and need not be considered further.
> The git -C approach is indifferent to the current directory, of course.
>
> GIT_WORK_TREE enters much too late in the process, and rather should handled first:
> if GIT_WORK_TREE is in the environment, cd to that first. Throw an error if that
> directory is not a valid worktree.
As I said, GIT_WORK_TREE without GIT_DIR is an invalid use-case. For
this reason, the first thing to do is find the database, and from there
work out the worktree. In the most common use-case it is the current
directory.
> I don't actually understand the use case of defining GIT_DIR or GIT_WORK_TREE to git gui,
> and I wonder what other bugs are lurking... maybe the better approach is to just abort if
> GIT_DIR or GIT_WORK_TREE are defined?
I lean towards setting GIT_DIR always. This is necessary, because Git
GUI can be run from a subdirectory of the worktree, and then changes
directory to the top-level. It must be ensured that the same GIT_DIR is
used that was detected from the subdirectory.
Now that the current directory is at the top-level of the worktree, we
could just not set GIT_WORK_TREE at all, provided that setting GIT_DIR
without GIT_WORK_TREE is a valid use-case for Git. I am not yet sure
about that.
-- Hannes
^ permalink raw reply
* Re: [PATCH v5 1/1] git-gui: restructure repository startup
From: Johannes Sixt @ 2026-05-06 7:15 UTC (permalink / raw)
To: Shroom Moo; +Cc: Mark Levedahl, git
In-Reply-To: <tencent_78B80FB7A0A42E464B3EF1841E2AF3C39509@qq.com>
Am 04.05.26 um 16:59 schrieb Shroom Moo:
> When git-gui is started inside a .git directory of a non-bare
> repository, it should treat the parent directory as the worktree,
> as it did before commit 2d92ab32fd (rev-parse: make --show-toplevel
> without a worktree an error, 2019-11-19). However, a bare repository
> or a separated gitdir without a worktree must be rejected early.
>
> Protect the previously unguarded calls to `git rev-parse
> --show-object-format` and `--show-toplevel`. Restructure the startup
> sequence to:
>
> - Check for a bare repository right after loading the config. If the
> repository is bare and the current subcommand does not allow bare
> repos (e.g. normal commit mode), show "Cannot use bare repository"
> and exit.
>
> - When `rev-parse --show-toplevel` fails and the repository is
> non-bare, the gitdir path ends with ".git", and we are inside that
> gitdir, use the parent directory as the worktree. This preserves
> the ability to start git-gui from within a regular repository’s
> .git directory, which was intentionally supported since 87cd09f43e56
> (git-gui: work from the .git dir, 2010-01-23).
>
> - Otherwise, show a descriptive error and exit.
Very good. This does things in the right order, IMO.
>
> - Wrap `rev-parse --show-object-format` in a catch to avoid a crash
> when the repository configuration is broken (e.g. core.worktree
> pointing to an invalid path).
Nice catch. This could be moved into its own patch. But it is acceptable
in this patch as it loosely fits the topic.
>
> Also removes the old `_prefix`‑based fallback that computed a relative
> path to the worktree top from a subdirectory, and the unconditional
> `[file dirname $_gitdir]` guess. Both are unnecessary now that
> `rev‑parse --show‑toplevel` directly provides the absolute top‑level
> path and we can `cd` to it. The guess is further unsafe in
> multi‑worktree setups, where a gitdir may have more than one worktree.
> The only remaining fallback is the explicit “.git directory” rule for
> non‑bare repositories, which mirrors the historical behaviour.
Nice cleanup.
>
> This fixes the fatal Tcl error when the working tree is missing, while
> keeping the .git startup feature and avoiding any automatic directory
> switching that could be dangerous in multi‑worktree setups.
Good!
However, this doesn't fix `git gui blame HEAD file` in a bare
repository, because `git branch --show-current` fails with an empty
GIT_WORK_TREE value. Fixing this needs to consider whether to set
GIT_DIR and GIT_WORK_TREE at all, as alluded to by Mark in a near-by
message. This is a separate topic.
>
> Signed-off-by: Shroom Moo <egg_mushroomcow@foxmail.com>
> ---
> git-gui/git-gui.sh | 72 +++++++++++++++++++++++++++++-----------------
> 1 file changed, 45 insertions(+), 27 deletions(-)
>
> diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
> index 23fe76e498..c06d85b8d9 100755
> --- a/git-gui/git-gui.sh
> +++ b/git-gui/git-gui.sh
> @@ -1129,7 +1129,8 @@ if {[catch {
> }]
> && [catch {
> # beware that from the .git dir this sets _gitdir to .
> - # and _prefix to the empty string
> + # and _prefix to the empty string; this is handled by
> + # the startup safety checks below
> set _gitdir [git rev-parse --git-dir]
> set _prefix [git rev-parse --show-prefix]
> } err]} {
> @@ -1142,8 +1143,20 @@ if {[catch {
> set picked 1
> }
>
> +if {![file isdirectory $_gitdir]} {
> + catch {wm withdraw .}
> + error_popup [strcat
> + [mc "Git directory not found:"] "\n\n$_gitdir\n\n" \
> + [mc "Please ensure GIT_DIR points to a valid Git repository"]]
> + exit 1
> +}
> +
> # Use object format as hash algorithm (either "sha1" or "sha256")
> -set hashalgorithm [git rev-parse --show-object-format]
> +if {[catch {set hashalgorithm [git rev-parse --show-object-format]} err]} {
> + catch {wm withdraw .}
> + error_popup [strcat [mc "Failed to determine hash algorithm:"] "\n\n$err"]
> + exit 1
> +}
> if {$hashalgorithm eq "sha1"} {
> set hashlength 40
> } elseif {$hashalgorithm eq "sha256"} {
> @@ -1160,46 +1173,50 @@ if {$_gitdir eq "."} {
> set _gitdir [pwd]
> }
>
> -if {![file isdirectory $_gitdir]} {
> - catch {wm withdraw .}
> - error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
> - exit 1
> -}
> # _gitdir exists, so try loading the config
> load_config 0
> apply_config
>
> -set _gitworktree [git rev-parse --show-toplevel]
> +# Handle bare repository early: if not allowed, abort
> +if {[is_bare] && ![is_enabled bare]} {
> + catch {wm withdraw .}
> + error_popup [strcat [mc "Cannot use bare repository:"] "\n\n" [file normalize $_gitdir]]
> + exit 1
> +}
>
> -if {$_prefix ne {}} {
> - if {$_gitworktree eq {}} {
> - regsub -all {[^/]+/} $_prefix ../ cdup
> - } else {
> - set cdup $_gitworktree
> - }
> - if {[catch {cd $cdup} err]} {
> - catch {wm withdraw .}
> - error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
> - exit 1
> +# Determine the working tree
> +if {[is_bare] && [is_enabled bare]} {
> + set _gitworktree {}
I strongly suggest to collapse this branch and the previous 'if
{[is_bare] && ![is_enabled bare]}' into a single 'if {[is_bare]}',
because then in the else-branch below we can be sure not to have a bare
repository.
> +} else {
> + if {[catch {set _gitworktree [git rev-parse --show-toplevel]} err]} {
> + # If we are inside a .git directory of a non-bare repo,
> + # the worktree is the parent directory
> + set inside_gitdir 0
> + catch {set inside_gitdir [git rev-parse --is-inside-git-dir]}
> + if {![is_bare] && $inside_gitdir eq {true} && [file tail [file normalize $_gitdir]] eq {.git}} {
Do we need to 'file normalize' before taking the 'file tail'? If not,
then the line would be shorter.
> + set _gitworktree [file normalize [file dirname $_gitdir]]
> + } else {
> + catch {wm withdraw .}
> + error_popup [strcat [mc "Cannot determine working tree:"] "\n\n$err"]
> + exit 1
> + }
> }
> - set _gitworktree [pwd]
> - unset cdup
> -} elseif {![is_enabled bare]} {
> - if {[is_bare]} {
> +
> + if {$_gitworktree eq {}} {
> catch {wm withdraw .}
> - error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
> + error_popup [mc "Cannot determine working tree (unexpected empty result)"]
> exit 1
> }
> - if {$_gitworktree eq {}} {
> - set _gitworktree [file dirname $_gitdir]
> - }
> +
> if {[catch {cd $_gitworktree} err]} {
> catch {wm withdraw .}
> - error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
> + error_popup [strcat [mc "Cannot move to working directory:"] "\n\n$err"]
> exit 1
> }
> set _gitworktree [pwd]
> }
> +
> +# Derive a human-readable repository name
> set _reponame [file split [file normalize $_gitdir]]
> if {[lindex $_reponame end] eq {.git}} {
> set _reponame [lindex $_reponame end-1]
> @@ -1207,6 +1224,7 @@ if {[lindex $_reponame end] eq {.git}} {
> set _reponame [lindex $_reponame end]
> }
>
> +# Export the final paths
> set env(GIT_DIR) $_gitdir
> set env(GIT_WORK_TREE) $_gitworktree
>
-- Hannes
^ permalink raw reply
* Re: [PATCH v6] revision.c: implement --max-count-oldest
From: Johannes Sixt @ 2026-05-06 6:45 UTC (permalink / raw)
To: Mirko Faina
Cc: Junio C Hamano, Jeff King, Jean-Noël Avila,
Patrick Steinhardt, Tian Yuchen, Ben Knoble, Chris Torek, git
In-Reply-To: <ce8d1ff49ef418ae3720265a124ef53a959d289e.1778017966.git.mroik@delayed.space>
Am 05.05.26 um 23:54 schrieb Mirko Faina:
> --max-count is a commit limiting option sets a maximum amount of commits
> to be shown. If a user wants to see only the first N commits of the
> history (the oldest commits) they'd have to do something like
>
> git log $(git rev-list HEAD | tail -n N | head -n 1)
>
> This is not very user-friendly.
>
> Teach get_revision() the --max-count-oldest option.
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> Since v5 I've reworded the commit message and rewrote the docs for
> --max-count-oldest to be clearer on its functionality.
>
> Documentation/rev-list-options.adoc | 5 ++
> revision.c | 77 +++++++++++++++++++++++++++--
> revision.h | 2 +
> t/t4202-log.sh | 14 ++++++
> 4 files changed, 95 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
> index 2d195a1474..9f857cabcc 100644
> --- a/Documentation/rev-list-options.adoc
> +++ b/Documentation/rev-list-options.adoc
> @@ -18,6 +18,11 @@ ordering and formatting options, such as `--reverse`.
> `--max-count=<number>`::
> Limit the output to _<number>_ commits.
>
> +`--max-count-oldest=<number>`::
> + Just like `--max-count=<number>`, it limits the output to _<number>_
> + commits. But instead of limiting to the first _<number>_ commits it
> + limits to the last _<number>_ commits.
> +
"Just like --max-count" is a surprising addendum in this sentence,
because the only thing they have in common is the limiting of commits,
which it repeats anyway. It's more like "Unlike --max-count, limits the
output to _<number>_ last commits."
BTW, this makes me think whether this kind of limiting could be
triggered by a negative argument to --max-count.
> `--skip=<number>`::
> Skip _<number>_ commits before starting to show the commit output.
>
> diff --git a/revision.c b/revision.c
> index 599b3a66c3..3aaa77ced5 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -2339,10 +2339,24 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
> }
>
> if ((argcount = parse_long_opt("max-count", argv, &optarg))) {
> + if (revs->max_count_type == 1)
> + die(_("can't use --max-count with --max-count-oldest"));
To help translators, the usual pattern is to say (here and later)
die(_("options '%s' and '%s' cannot be used together"),
"--max-count", "--max-count-oldest");
> revs->max_count = parse_count(optarg);
> revs->no_walk = 0;
> + revs->max_count_type = 0;
> return argcount;
> + } else if ((argcount = parse_long_opt("max-count-oldest", argv, &optarg))) {
> + if (revs->max_count_type == 0 && revs->max_count != -1)
> + die(_("can't use --max-count with --max-count-oldest"));
> + if (revs->skip_count > 0)
> + die(_("con't use --max-count-oldest with --skip"));
> + revs->max_count = parse_count(optarg);
> + revs->no_walk = 0;
> + revs->max_count_type = 1;
> + revs->max_count_stage = 0;
> } else if ((argcount = parse_long_opt("skip", argv, &optarg))) {
> + if (revs->max_count_type == 1)
> + die(_("con't use --max-count-oldest with --skip"));
> revs->skip_count = parse_count(optarg);
> return argcount;
> } else if ((*arg == '-') && isdigit(arg[1])) {
> @@ -4521,15 +4535,68 @@ static struct commit *get_revision_internal(struct rev_info *revs)
> return c;
> }
>
> +static void retrieve_oldest_commits(struct rev_info *revs,
> + struct commit_list **queue)
> +{
> + struct commit *c;
> + int max_count = revs->max_count;
> + int queuei_count = 0;
> + int queueo_count = 0;
> + struct commit_list *queueo = NULL;
> + struct commit_list *queuei = NULL;
> + struct commit_list *reversed_queue = NULL;
> +
> + revs->max_count = -1;
> + while ((c = get_revision_internal(revs))) {
> + c->object.flags &= ~SHOWN;
> + commit_list_insert(c, &queuei);
> + queuei_count++;
> + while (queuei_count + queueo_count > max_count) {
> + if (!queueo_count) {
> + while (queuei_count > 0) {
> + c = pop_commit(&queuei);
> + queuei_count--;
> + commit_list_insert(c, &queueo);
> + queueo_count++;
> + }
> + }
> + pop_commit(&queueo);
> + queueo_count--;
> + }
> + }
> +
> + while ((c = pop_commit(&queueo)))
> + commit_list_insert(c, &reversed_queue);
> + while ((c = pop_commit(&queuei)))
> + commit_list_insert(c, &queueo);
> + while ((c = pop_commit(&queueo)))
> + commit_list_insert(c, &reversed_queue);
> +
> + while ((c = pop_commit(&reversed_queue)))
> + commit_list_insert(c, queue);
> +}
> +
> struct commit *get_revision(struct rev_info *revs)
> {
> struct commit *c;
> struct commit_list *reversed;
> + struct commit_list *queue = NULL;
> +
> + if (revs->max_count_type == 1 && !revs->max_count_stage) {
> + retrieve_oldest_commits(revs, &queue);
> + commit_list_free(revs->commits);
> + revs->commits = queue;
> + revs->max_count_stage = 1;
> + }
>
> if (revs->reverse) {
> reversed = NULL;
> - while ((c = get_revision_internal(revs)))
> - commit_list_insert(c, &reversed);
> + if (revs->max_count_type == 1)
> + while ((c = pop_commit(&revs->commits)))
> + commit_list_insert(c, &reversed);
> + else
> + while ((c = get_revision_internal(revs)))
> + commit_list_insert(c, &reversed);
> commit_list_free(revs->commits);
> revs->commits = reversed;
> revs->reverse = 0;
I would have expected that this kind of commit counting is handled at
the same spot where --max-count is handled, i.e., in
get_revision_internal(). It could make a difference in combination with
sorting options, --boundary, and --graph. The goal is that --max-count
and --max-count-oldest behave the same in this regard. (But I am in no
way an expert of the revision walker.)
> @@ -4543,7 +4610,11 @@ struct commit *get_revision(struct rev_info *revs)
> return c;
> }
>
> - c = get_revision_internal(revs);
> + if (revs->max_count_stage)
> + c = pop_commit(&revs->commits);
> + else
> + c = get_revision_internal(revs);
> +
> if (c && revs->graph)
> graph_update(revs->graph, c);
> if (!c) {
> diff --git a/revision.h b/revision.h
> index 584f1338b5..e157463cb1 100644
> --- a/revision.h
> +++ b/revision.h
> @@ -309,6 +309,8 @@ struct rev_info {
> /* special limits */
> int skip_count;
> int max_count;
> + unsigned int max_count_type:1;
> + unsigned int max_count_stage:1;
> timestamp_t max_age;
> timestamp_t max_age_as_filter;
> timestamp_t min_age;
-- Hannes
^ permalink raw reply
* [PATCH] rebase: ignore non-branch update-refs
From: mail @ 2026-05-06 2:39 UTC (permalink / raw)
To: git; +Cc: Abhinav Gupta, Derrick Stolee, Junio C Hamano
From: Abhinav Gupta <mail@abhinavg.net>
The following Git configuration breaks git rebase --update-refs:
[rebase]
instructionFormat = %s%d
The '%d' format requests all available decorations for a commit,
filling the global decoration table with all of them,
which --update-refs then uses to populate 'update-ref' instructions
in the rebase todo list.
Specifically, this results in the following instruction:
update-ref HEAD
The todo parser then rejects the instruction:
error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD
error: invalid line 3: update-ref HEAD
To fix, ignore decorations that are not local branches
when scanning through the table.
This filtering matches the documented contract:
Automatically force-update any branches that point to commits [..]
Signed-off-by: Abhinav Gupta <mail@abhinavg.net>
---
sequencer.c | 10 ++++++++++
t/t3404-rebase-interactive.sh | 22 ++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index b7d8dca47f..25bcfc5da0 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,
const char *path;
size_t base_offset = ctx->buf->len;
+ /*
+ * The global decoration table may contain names loaded by
+ * a previous pretty format such as "%d".
+ * This will result in refs such as "HEAD" being present.
+ */
+ if (decoration->type != DECORATION_REF_LOCAL) {
+ decoration = decoration->next;
+ continue;
+ }
+
/*
* If the branch is the current HEAD, then it will be
* updated by the default rebase behavior.
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 3e44562afa..d58236f0eb 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1960,6 +1960,28 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '
)
'
+test_expect_success '--update-refs ignores non-branch decorations' '
+ test_when_finished "git branch -D update-refs" &&
+ test_when_finished "git branch -D third" &&
+ test_when_finished "git checkout primary" &&
+ git checkout -B update-refs no-conflict-branch &&
+ git branch -f third HEAD~1 &&
+ (
+ set_cat_todo_editor &&
+
+ # rebase.instructionFormat=%d loads normal log decorations before
+ # --update-refs adds its branch placeholders.
+ # The placeholder scan must still ignore symbolic decorations,
+ # because "update-ref HEAD" is not a valid branch update.
+ test_must_fail git -c rebase.instructionFormat="%s%d" \
+ rebase -i --update-refs primary >todo &&
+
+ test_grep "^update-ref refs/heads/third$" todo &&
+ test_grep ! "^update-ref refs/heads/update-refs$" todo &&
+ test_grep ! "^update-ref HEAD$" todo
+ )
+'
+
test_expect_success '--update-refs updates refs correctly' '
git checkout -B update-refs no-conflict-branch &&
git branch -f base HEAD~4 &&
base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
--
2.54.0
^ permalink raw reply related
* [BUG] "git diff --word-diff" gives a diff while they are only space changes
From: Vincent Lefevre @ 2026-05-06 1:09 UTC (permalink / raw)
To: git
Consider the following two 5-line files:
file1:
1
2
3
2
4
file2:
1
2
3
2
4
On these files, "git diff --word-diff file1 file2" gives
--- a/file1
+++ b/file2
@@ -1,5 +1,5 @@
1
[-2-]
[-3-]
2
{+3+}
{+ 2+}
4
instead of
--- a/file1
+++ b/file2
@@ -1,5 +1,5 @@
1
2
3
2
4
(e.g. as output by GNU wdiff 1.2.2).
Equivalently, the following command can be used under bash or zsh:
git diff --word-diff <(printf "1\n2\n3\n 2\n4\n") \
<(printf "1\n 2\n 3\n 2\n 4\n")
Tested with git 2.39.5 (Debian 12), 2.53.0 (Debian unstable) and
2.54.0 (Termux/Android).
Issue initially found with
git show --word-diff 1bf0b214deff2d0ccdcef3b2a4723369e014de3d
and more precisely
git show --word-diff 1bf0b214deff2d0ccdcef3b2a4723369e014de3d attach.c
in the Mutt Git repository.
--
Vincent Lefèvre <vincent@vinc17.net> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Pascaline project (LIP, ENS-Lyon)
^ permalink raw reply
* Re: [PATCH v2 11/11] ci: run expensive tests on push builds to integration branches
From: Junio C Hamano @ 2026-05-05 23:07 UTC (permalink / raw)
To: Derrick Stolee
Cc: Johannes Schindelin via GitGitGadget, git,
Torsten Bögershausen, Jeff King, Johannes Schindelin
In-Reply-To: <xmqq5x52nhg6.fsf@gitster.g>
(in GMail web interface, excuse typos)
https://github.com/git/git/actions/runs/25366120610/job/74377320625
We seem to be hitting the same _Generic error in various (but not all) jobs
/usr/include/x86_64-linux-gnu/sys/cdefs.h:838:3: note: expanded from
macro '__glibc_const_generic'
838 | _Generic (0 ? (PTR) : (void *) 1, \
| ^
Error: list-objects-filter-options.c:222:10: '_Generic' is a C11
extension [-Werror,-Wc11-extensions]
I thought we updated the codebase to avoid stripping away constness
with strchr() and friends, but the error seems to be more like one
hand in the system passing -Wc11-extensions to stick to older version
of C and the other hand in the system that uses _Generic to implement
the const/non-const variants of strchr() in the system header not
knowing that the other tells C11 const-preserving strchr() should not
be used?
2026年5月5日(火) 21:56 Junio C Hamano <gitster@pobox.com>:
>
> Derrick Stolee <stolee@gmail.com> writes:
>
> > On 5/4/2026 1:08 PM, Johannes Schindelin via GitGitGadget wrote:
> >> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >>
> >> Derrick Stolee suggested [1] that expensive tests should be run at a
> >> regular cadence rather than on every PR iteration. Gate GIT_TEST_LONG
> >> on push builds to the integration branches (next, master, main, maint)
> >> so that the EXPENSIVE prereq is satisfied there but not during PR
> >> validation, where the extra minutes of wall-clock time do not justify
> >> themselves.
> > I like that this will be run as part of regular updates to the
> > important branches. The important bit after that is whether or
> > not a human pays attention to the signal of these builds.
> >
> > Junio: Do you pay attention to CI breaks when you push to
> > 'master'?
>
> Well, it is way too late to notice breakage when the faulty update
> hits 'master'. CI failures should be noticed before breakage hits
> 'next'.
>
> I often notice and complain when I see failures on 'seen', and
> sometimes I help original submitter by bisecting, but I do not
> necessarily have enough time and bandwidth to help everybody.
>
> Quite honestly, the best place to give widest test coverage is much
> closer to the source of the problems than in my tree and mixed with
> other topics, i.e., at individual contributor's CI. That way, I
> presume that GitGitGadget can also help submitters avoid sending a
> faulty series, reducing the load on the list and the maintainer.
>
> Ideally the CI tests by the integrator should only be catching any
> mismerges and unexpected inter-topic interactions, as they cannot be
> caught by contributor's standalone tests, so I do not mind widening
> coverage of CI tests when I push the integration results out. But
> so far, the majority of what I have seen and reported back to the
> list have been something that the authors should be equipped to spot
> in their topic without getting mixed with other topics into any
> integration branches.
>
> > One way to help this procedure could be to have GitHub CI
> > failures trigger new issues, which could then be more easily
> > viewed and noticed by the community watching the repo. This
> > is of course out-of-scope for this patch series, but could be
> > considered in the future.
>
> I think a better way to help would be to arrange the workflow so
> that we do not even have to trigger an issue, and stop before the
> patches leave the original authors' hand. They can of course ask
> for help saying "here is my topic in my fork of the repository and
> failing in this way for macOS that I do not have access to. Could
> anybody help me figuring out what macOS peculiarity my changes are
> tickling?", or something like that.
>
> It would be best to find problems early, and make it easier for
> individual contributors to help each other by having a concrete CI
> failure reports in their forks that they can point at when they ask
> for help. And CI run when I push 'seen' or 'master' out would not
> help as much as CI run when they publish their forked branches would.
>
> By the way, please expect slow responses as I am (officially) still
> mostly offline for the rest of the week.
>
> Thanks.
>
^ permalink raw reply
* [PATCH 4/4] parse-options: clarify PARSE_OPT_NONEG does not reject negative numbers
From: Michael Montalbo via GitGitGadget @ 2026-05-05 23:02 UTC (permalink / raw)
To: git; +Cc: Michael Montalbo, Michael Montalbo
In-Reply-To: <pull.2105.git.1778022144.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
The name "NONEG" can be misread as "no negative [values]" when it
actually means "no [boolean] negation" (the --no-* form).
When --inter-hunk-context and -U/--unified were converted from a
custom parser to OPT_INTEGER_F with PARSE_OPT_NONEG in d473e2e0e8
and 16ed6c97cc, the implicit rejection of negative values (via
isdigit() in the old opt_arg() parser) was silently lost. The
previous commits in this series fix the resulting bugs.
Add a clarifying note to the flag documentation.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
parse-options.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/parse-options.h b/parse-options.h
index 706de9729f..c0a3a3dcae 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -116,7 +116,10 @@ typedef int parse_opt_subcommand_fn(int argc, const char **argv,
* mask of parse_opt_option_flags.
* PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)
* PARSE_OPT_NOARG: says that this option does not take an argument
- * PARSE_OPT_NONEG: says that this option cannot be negated
+ * PARSE_OPT_NONEG: says that this option cannot be negated (i.e.
+ * prevents --no-<option> boolean form). Does not reject
+ * negative numeric values like --option=-1. Use
+ * OPT_UNSIGNED for options that must be non-negative.
* PARSE_OPT_HIDDEN: this option is skipped in the default usage, and
* shown only in the full usage.
* PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default
--
gitgitgadget
^ permalink raw reply related
* [PATCH 3/4] xdiff: guard against negative context lengths
From: Michael Montalbo via GitGitGadget @ 2026-05-05 23:02 UTC (permalink / raw)
To: git; +Cc: Michael Montalbo, Michael Montalbo
In-Reply-To: <pull.2105.git.1778022144.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
The xdemitconf_t fields ctxlen and interhunkctxlen are typed as long
(signed), but negative values are not meaningful for context line
counts. Unlike the diff_options fields changed in the previous two
commits, these cannot be converted to unsigned because the xdiff
arithmetic relies on signed subtraction:
s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
If ctxlen were unsigned long, the signed operand would be implicitly
converted to unsigned, and the subtraction would wrap to a large
positive value when i1 < ctxlen, defeating the XDL_MAX clamp. The
signed type is required for correct context-window calculations.
The previous two commits reject negative values at the parse layer
for --inter-hunk-context and -U/--unified, so negative values should
no longer reach xdiff in normal use. Add BUG() guards at the top of
xdl_get_hunk() as defense in depth to catch programming errors in
current or future callers that bypass option parsing.
xdl_get_hunk() is called by both xdl_emit_diff() and
xdl_call_hunk_func(), so a single guard covers all xdiff consumers.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
xdiff/xemit.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 04f7e9193b..7cd9cf0a44 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -46,12 +46,20 @@ static long saturating_add(long a, long b)
xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
{
xdchange_t *xch, *xchp, *lxch;
- long max_common = saturating_add(saturating_add(xecfg->ctxlen,
- xecfg->ctxlen),
- xecfg->interhunkctxlen);
- long max_ignorable = xecfg->ctxlen;
+ long max_common;
+ long max_ignorable;
long ignored = 0; /* number of ignored blank lines */
+ if (xecfg->ctxlen < 0)
+ BUG("negative context length: %ld", xecfg->ctxlen);
+ if (xecfg->interhunkctxlen < 0)
+ BUG("negative inter-hunk context length: %ld", xecfg->interhunkctxlen);
+
+ max_common = saturating_add(saturating_add(xecfg->ctxlen,
+ xecfg->ctxlen),
+ xecfg->interhunkctxlen);
+ max_ignorable = xecfg->ctxlen;
+
/* remove ignorable changes that are too far before other changes */
for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
xch = xchp->next;
--
gitgitgadget
^ permalink raw reply related
* [PATCH 2/4] diff: reject negative values for -U/--unified
From: Michael Montalbo via GitGitGadget @ 2026-05-05 23:02 UTC (permalink / raw)
To: git; +Cc: Michael Montalbo, Michael Montalbo
In-Reply-To: <pull.2105.git.1778022144.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
Passing a negative value to -U is silently accepted and produces
corrupt unified diff output with malformed hunk headers:
$ git log -1 -p -U-500 -- GIT-VERSION-GEN | grep '^@@'
@@ -503,999- +503,999- @@
Line 503 of a 106-line file, count "999-" is not a valid integer.
The config variable diff.context already rejects negative values, but
the command line callback diff_opt_unified() uses strtol() with no
range check.
Change the type of diff_options.context and its static default from
int to unsigned int, matching the change to interhunkcontext in the
previous commit. The type change requires reworking the callback and
config parsing to validate in a local variable before assigning to
the now-unsigned field.
Unlike --inter-hunk-context which could be converted to OPT_UNSIGNED,
-U needs OPT_CALLBACK_F for PARSE_OPT_OPTARG (bare -U with no value
enables patch output). Add a range check in the callback instead.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
diff.c | 12 ++++++++----
diff.h | 2 +-
t/t4055-diff-context.sh | 5 +++++
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/diff.c b/diff.c
index 5df28e49c5..1771b2c444 100644
--- a/diff.c
+++ b/diff.c
@@ -60,7 +60,7 @@ static int diff_suppress_blank_empty;
static enum git_colorbool diff_use_color_default = GIT_COLOR_UNKNOWN;
static int diff_color_moved_default;
static int diff_color_moved_ws_default;
-static int diff_context_default = 3;
+static unsigned int diff_context_default = 3;
static unsigned int diff_interhunk_context_default;
static char *diff_word_regex_cfg;
static struct external_diff external_diff_cfg;
@@ -382,9 +382,10 @@ int git_diff_ui_config(const char *var, const char *value,
return 0;
}
if (!strcmp(var, "diff.context")) {
- diff_context_default = git_config_int(var, value, ctx->kvi);
- if (diff_context_default < 0)
+ int val = git_config_int(var, value, ctx->kvi);
+ if (val < 0)
return -1;
+ diff_context_default = val;
return 0;
}
if (!strcmp(var, "diff.interhunkcontext")) {
@@ -5924,9 +5925,12 @@ static int diff_opt_unified(const struct option *opt,
BUG_ON_OPT_NEG(unset);
if (arg) {
- options->context = strtol(arg, &s, 10);
+ long val = strtol(arg, &s, 10);
if (*s)
return error(_("%s expects a numerical value"), "--unified");
+ if (val < 0)
+ return error(_("%s expects a non-negative integer"), "--unified");
+ options->context = val;
}
enable_patch_output(&options->output_format);
diff --git a/diff.h b/diff.h
index 033d633db4..bb5cddaf34 100644
--- a/diff.h
+++ b/diff.h
@@ -294,7 +294,7 @@ struct diff_options {
enum git_colorbool use_color;
/* Number of context lines to generate in patch output. */
- int context;
+ unsigned int context;
unsigned int interhunkcontext;
diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh
index 1384a81957..b26f6eea7c 100755
--- a/t/t4055-diff-context.sh
+++ b/t/t4055-diff-context.sh
@@ -82,6 +82,11 @@ test_expect_success 'negative integer config parsing' '
test_grep "bad config variable" output
'
+test_expect_success '-U-1 is rejected' '
+ test_must_fail git diff -U-1 2>err &&
+ test_grep "expects a non-negative integer" err
+'
+
test_expect_success '-U0 is valid, so is diff.context=0' '
test_config diff.context 0 &&
git diff >output &&
--
gitgitgadget
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox