* Re: git hook question
From: Junio C Hamano @ 2026-06-01 5:33 UTC (permalink / raw)
To: Jeff King; +Cc: Wesley Schwengle, Git maillinglist
In-Reply-To: <20260529052141.GA1099450@coredump.intra.peff.net>
Jeff King <peff@peff.net> writes:
> I don't think so; the command is expected to handle (or ignore) the
> arguments as appropriate.
We should also caution that the command is expected to handle not
just the arguments but its standard input. Not reading any and
exiting may be a no-no for some hooks.
And unlike command line arguments, there is no handy way to say "I
do not care what the input is" (other than putting "cat >/dev/null;"
in front of what you really want to do, that is).
^ permalink raw reply
* Re: [PATCH 0/5] git son: add command to create independent child repositories
From: Junio C Hamano @ 2026-06-01 5:33 UTC (permalink / raw)
To: Evan Haque via GitGitGadget; +Cc: git, Evan Haque
In-Reply-To: <pull.2122.git.1779814052.gitgitgadget@gmail.com>
"Evan Haque via GitGitGadget" <gitgitgadget@gmail.com> writes:
> Motivation
> ==========
>
> When spinning off a new project that is related to an existing repository,
> there is no built-in way to create a child repository that maintains a link
> back to its parent without the tight coupling of submodules. Submodules pin
> the child to a specific commit and require the parent to track the child in
> its index, which is too heavyweight when the child is meant to be fully
> independent.
>
> The typical workflow today is manual: git init, git remote add, update
> .gitignore — three steps that are easy to forget or get wrong. git son
> automates this and establishes a lightweight convention for the parent-child
> relationship: a remote named parent in the child, and nothing in the parent
> except an ignore rule.
Although I am personally not interested in this topic even for local
repository use, I should point out that using this tool to create
this combination of repositories would not be useful for other
people who want to clone your arrangement. Other than leaving
comments in the parent project, there seems to be no way to specify
how or where the child projects should be cloned within the working
tree of the parent project.
Since Git is primarily a tool for collaboration among people with
their own repositories, I fail see how this feature is something we
should ship to all Git users as part of the core distribution.
Thanks.
^ permalink raw reply
* Re: [PATCH v3 0/6] [RFC] diff: add diff.<driver>.process for external hunk providers
From: Michael Montalbo @ 2026-06-01 4:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Michael Montalbo via GitGitGadget, git
In-Reply-To: <xmqq5x43dfk4.fsf@gitster.g>
On Sun, May 31, 2026 at 3:44 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > Language-aware diff tools (e.g., Difftastic) and format-specific analyzers
> > can produce better line matching than Git's builtin diff algorithm, but
> > diff.<driver>.command replaces Git's output entirely, losing downstream
> > features like word diff, function context, color, and blame.
>
> This seems to break CI on Windows; take a look at
>
> https://github.com/git/git/actions/runs/26709491830/job/78717295153
>
> for an example.
>
> Thanks.
Thanks for the heads up. Interestingly, I think Windows exposed a latent issue
with sub-process startup dying instead of erroring out when there is a space in
the path.
I've submitted https://lore.kernel.org/git/pull.2133.git.1780287309846.gitgitgadget@gmail.com/T/#u
which I believe addresses the issue, however it seems like there are
some potentially
unrelated CI failures going on right now which is making verification
a bit harder.
^ permalink raw reply
* [PATCH] sub-process: use gentle handshake to avoid die() on startup failure
From: Michael Montalbo via GitGitGadget @ 2026-06-01 4:15 UTC (permalink / raw)
To: git; +Cc: Michael Montalbo, Michael Montalbo
From: Michael Montalbo <mmontalbo@gmail.com>
When the configured subprocess command contains shell metacharacters
(such as a space), prepare_shell_cmd() wraps it in "sh -c <cmd>".
The shell itself always starts successfully, so start_command()
returns zero even if the tool inside does not exist. The subsequent
handshake then reads from a dead pipe and calls die() via the
non-gentle packet_read_line(), killing the parent process instead of
letting it handle the error.
Before this change, a missing filter process at a path containing
spaces produces a confusing error:
$ git -c filter.lfs.process="/path with space/tool" \
-c filter.lfs.required=true add file.txt
fatal: the remote end hung up unexpectedly
After this change, the proper error is reported:
$ git ... add file.txt
error: initialization for subprocess '/path with space/tool' failed
fatal: file.txt: clean filter 'lfs' failed
Switch the subprocess handshake from the dying packet_read_line()
to packet_read_line_gently() so that a process that exits during
startup produces an error return instead of killing the caller.
This affects any subprocess consumer whose command path contains
spaces. On Windows this routinely happens because programs live
under "C:/Program Files/...", and MSYS2 path conversion can rewrite
absolute paths to include that prefix. On POSIX it triggers
whenever the configured path naturally contains a space or other
metacharacter. convert.c (filter.<driver>.process, used by git-lfs
and custom clean/smudge filters) is the primary affected consumer.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
sub-process: use gentle handshake to avoid die() on startup failure
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2133%2Fmmontalbo%2Fmm%2Fsubprocess-handshake-fix-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2133/mmontalbo/mm/subprocess-handshake-fix-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2133
sub-process.c | 11 ++++++-----
t/t0021-conversion.sh | 17 +++++++++++++++++
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/sub-process.c b/sub-process.c
index 83bf0a0e82..22c68bd10d 100644
--- a/sub-process.c
+++ b/sub-process.c
@@ -132,18 +132,19 @@ static int handshake_version(struct child_process *process,
if (packet_flush_gently(process->in))
return error("Could not write flush packet");
- if (!(line = packet_read_line(process->out, NULL)) ||
+ if (packet_read_line_gently(process->out, NULL, &line) <= 0 ||
!skip_prefix(line, welcome_prefix, &p) ||
strcmp(p, "-server"))
return error("Unexpected line '%s', expected %s-server",
line ? line : "<flush packet>", welcome_prefix);
- if (!(line = packet_read_line(process->out, NULL)) ||
+ if (packet_read_line_gently(process->out, NULL, &line) <= 0 ||
!skip_prefix(line, "version=", &p) ||
strtol_i(p, 10, chosen_version))
return error("Unexpected line '%s', expected version",
line ? line : "<flush packet>");
- if ((line = packet_read_line(process->out, NULL)))
- return error("Unexpected line '%s', expected flush", line);
+ if (packet_read_line_gently(process->out, NULL, &line) < 0 || line)
+ return error("Unexpected line '%s', expected flush",
+ line ? line : "<read error>");
/* Check to make sure that the version received is supported */
for (i = 0; versions[i]; i++) {
@@ -171,7 +172,7 @@ static int handshake_capabilities(struct child_process *process,
if (packet_flush_gently(process->in))
return error("Could not write flush packet");
- while ((line = packet_read_line(process->out, NULL))) {
+ while (packet_read_line_gently(process->out, NULL, &line) > 0) {
const char *p;
if (!skip_prefix(line, "capability=", &p))
continue;
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
index f0d50d769e..033b00a364 100755
--- a/t/t0021-conversion.sh
+++ b/t/t0021-conversion.sh
@@ -857,6 +857,23 @@ test_expect_success 'invalid process filter must fail (and not hang!)' '
)
'
+test_expect_success 'missing process filter with space in path does not die' '
+ test_config_global filter.protocol.process "/non existent/tool" &&
+ test_config_global filter.protocol.required true &&
+ rm -rf repo &&
+ mkdir repo &&
+ (
+ cd repo &&
+ git init &&
+
+ echo "*.r filter=protocol" >.gitattributes &&
+
+ cp "$TEST_ROOT/test.o" test.r &&
+ test_must_fail git add . 2>git-stderr.log &&
+ test_grep "clean filter.*protocol.*failed" git-stderr.log
+ )
+'
+
test_expect_success 'delayed checkout in process filter' '
test_config_global filter.a.process "test-tool rot13-filter --log=a.log clean smudge delay" &&
test_config_global filter.a.required true &&
base-commit: 29bd7ed5127255713c1ac2f43b7c6f257d7b4594
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v2] doc: fix typos via codespell
From: Junio C Hamano @ 2026-06-01 1:16 UTC (permalink / raw)
To: Andrew Kreimer; +Cc: git
In-Reply-To: <20260531184428.55905-1-algonell@gmail.com>
Andrew Kreimer <algonell@gmail.com> writes:
> diff --git a/midx-write.c b/midx-write.c
> index 561e9eedc0..19e1cd10b7 100644
> --- a/midx-write.c
> +++ b/midx-write.c
> @@ -1461,7 +1461,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);
You seem to have lost the following hunk from the previous round
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
but it is OK as the same fix appears in 'master' thanks to 22235136
(midx-write: handle noop writes when converting incremental chains,
2026-05-19).
Other than that, I made sure that there is no other changes since
the initial patch other than that the changes to po/ and git-gui/ no
longer exist in the topic, which matches what the proposed commit
log says.
However, there are things that BREAK tests.
> 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...
The edit above is made to a STRING that is enclosed inside a pair of
single quote. If we want to use "won't", we would need to write "We
won'\''t", but while it may be syntactically correct as a part of
shell script, it is a pointless change, as the target audience wants
to see this line as if it is just a plain text.
"We will not" would be acceptable,
> 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
Ditto. "did not 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
Ditto.
> # --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
Ditto.
It is a bit disappointing that the changes were guided by a tool
called Codespell, yet apparently the tool does not seem to
understand the syntax rules of the language the files it is
modifying is written in X-<.
Thanks.
^ permalink raw reply related
* Re: [PATCH] describe: fix --exclude, --match with --contains and --all
From: Junio C Hamano @ 2026-06-01 0:40 UTC (permalink / raw)
To: Tuomas Ahola; +Cc: Jacob Keller, git, Jacob Keller
In-Reply-To: <20260531234644.97LRl%taahol@utu.fi>
Tuomas Ahola <taahol@utu.fi> writes:
> Junio C Hamano <gitster@pobox.com> wrote:
>
>> It is curious that this fails in some but not all CI jobs, and even
>> more curious that these failures look the same.
>>
>> e.g., https://github.com/git/git/actions/runs/26671595367/job/78615760984#step:4:1984
>>
>> +++ diff -u expect actual
>> --- expect 2026-05-30 02:21:23
>> +++ actual 2026-05-30 02:21:23
>> @@ -1 +1 @@
>> -branch_A
>> +remotes/origin/remote_branch_A
>> error: last command exited with $?=1
>> not ok 70 - describe --contains --all --exclude
>> #
>> # echo "branch_A" >expect &&
>> # tagged_commit=$(git rev-parse "refs/tags/A^0") &&
>> # git describe --contains --all --exclude="A" --exclude="c" --exclude="test*" $tagged_commit >actual &&
>> # test_cmp expect actual
>>
>> Rings any bell?
>
> That's way out of my wheelhouse but this seems to fix the failure
> for Alpine at least:
>
> -----8<-----
>
> diff --git a/builtin/name-rev.c b/builtin/name-rev.c
> index d6594ada53..1776ffab46 100644
> --- a/builtin/name-rev.c
> +++ b/builtin/name-rev.c
> @@ -416,7 +416,7 @@ static void name_tips(struct mem_pool *string_pool)
> * Try to set better names first, so that worse ones spread
> * less.
> */
> - QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
> + STABLE_QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
> for (i = 0; i < tip_table.nr; i++) {
> struct tip_table_entry *e = &tip_table.table[i];
> if (e->commit) {
Ah, OK, when the test has multiple candidates with the same score,
of course emitting any one of them as the answer is a valid and
correctly working program.
So switching to stable-qsort here may "fix" the test breakage, but
it makes the real-world use cases worse, doesn't it? When any one
of the solutions with the same "goodness" is acceptable, the change
makes the code behave as if the elements in the table before they
are sorted have an "if same score, earlier the better" kind of
relationship between them.
I would have preferred to see a tweak on the test side to avoid
having more than one answer of the same goodness, or perhaps list
all the possible acceptable answers and instead of using test_cmp to
check for the exact answer, take any of the acceptable ones, or
something like that.
Thanks.
^ permalink raw reply
* Re: [PATCH] prio-queue: use cascade-down sift for faster extract-min
From: Junio C Hamano @ 2026-06-01 0:09 UTC (permalink / raw)
To: René Scharfe, Kristofer Karlsson via GitGitGadget
Cc: git, Kristofer Karlsson
In-Reply-To: <pull.2132.git.1780250236304.gitgitgadget@gmail.com>
"Kristofer Karlsson via GitGitGadget" <gitgitgadget@gmail.com>
writes:
I'll add René the recipients, as _replace() was added by him as
optimization, so "this new one is functionally equivalent to the
original" somewhat misses the point, even though we may all agree
that the change is a very good one overall in the end when we look
at the entire picture.
> From: Kristofer Karlsson <krka@spotify.com>
>
> Replace the standard sift-down in prio_queue_get() with a
> cascade-down approach.
>
> The standard approach places the last array element at the root,
> then sifts it down. At each level this requires two comparisons
> (left vs right child, then element vs winner) and, when the
> element is larger, a swap (three 16-byte copies).
>
> The cascade approach instead promotes the smaller child into the
> vacant root slot at each level — one comparison and one copy.
> The vacancy sinks to a leaf, where the last array element is
> placed and sifted up if needed — typically zero levels since the
> last array element tends to be large.
>
> In the common case, work per extract drops from 2d comparisons
> + 3d copies to d comparisons + d copies: roughly half the
> comparisons and a third of the data movement. The sift-up phase
> can add work when the last element is smaller than ancestors of
> the leaf vacancy, but this is rare in practice.
>
> Simplify prio_queue_replace() to a plain get+put sequence. This
> is semantically equivalent: the old implementation wrote to slot 0
> and sifted down, which has the same observable effect as removing
> the root and inserting a new element. No caller observes queue
> state between the two operations. The previous implementation
> shared sift_down_root() with get, but the cascade approach no
> longer accommodates that cleanly since sift_down_root() now
> expects the element to reinsert at queue->array[queue->nr], left
> there by prio_queue_get() after decrementing nr. This is fine in
> practice: replace is only called from pop_most_recent_commit()
> (fetch-pack, object-name, walker) and show-branch — none of
> which appear in any hot path.
>
> A synthetic benchmark (10 rounds of 10M put+get cycles, ascending
> integer keys, CPU-pinned, median of 3 runs, same compiler and
> Makefile flags) shows consistent improvement across all queue
> sizes, with no regressions:
>
> queue width baseline cascade speedup
> ------------------------------------------------
> 10 4.32s 3.97s 1.09x
> 100 7.95s 6.49s 1.23x
> 1,000 11.30s 9.66s 1.17x
> 10,000 16.34s 14.15s 1.16x
> 100,000 21.43s 18.66s 1.15x
>
> With descending keys (worst case — the last element always sinks
> to a leaf in both approaches) the cascade still wins slightly
> (1-4%) by replacing swaps with copies, and never regresses.
>
> In end-to-end git commands the improvement is modest because
> sift_down_root is only ~8% of total runtime. Profiling
> rev-list --count on a 2.5M-commit monorepo shows sift_down_root
> dropping from 8.2% to 0.4% of total runtime. The improvement
> scales with DAG width: wider DAGs produce larger priority queues,
> amplifying the per-level savings. In small or narrow repos the
> queues stay shallow and the effect is negligible.
>
> Signed-off-by: Kristofer Karlsson <krka@spotify.com>
> ---
> prio-queue: use cascade-down sift for faster extract-min
>
> Hi, I am not sure this is just noise or not but I thought it at least
> was interesting.
>
> I looked into the internals of prio_queue and found it was technically
> doing too much work and could be simplified/optimized. I found I could
> optimize it by ~20% for the common case (adding commits that would
> typically end up far back in the queue) but only ~1% for the reverse
> case (adding things to the front of the prio queue). The average speedup
> is somewhere in between I suppose. That said, this is not really the
> bottleneck so the overall boost seems to be around ~3-4% improvement for
> repos with wide DAGs.
>
> I would normally classify this as not urgent or important, but I think
> the advantage is that the change is very small and simple and it already
> has good unit tests (t/unit-tests/u-prio-queue.c).
>
> With that said, here are the details:
>
> The prio_queue_get impl is based on removing the root entry, then moving
> the very last element into the root slot, then sifting it down into the
> right place. This uses both comparisons between sibling elements in the
> heap as well as comparisons between the element to add and one of the
> siblings. Then it uses swap operations to move things correctly.
>
> This patch instead promotes the smaller child upward at each level,
> leaving a vacancy that sinks to a leaf, then places the removed element
> there with a short sift-up to keep the heap balanced.
>
> We can analytically compare this - for a sift-distance of d we can
> reason about the number of operations to execute.
>
> Before: 2d comparisons + 3d copies
> After: d comparisons + d copies
>
>
> After changing sift_down in this way, the replace operation can't simply
> depend on it anymore, so I reimplemented it as a sequence of get + put.
> This is technically correct but maybe not as efficient. However, I am
> not sure that it matters, since I couldn't see any usage of the replace
> operation in any hot path.
>
> Performance: Profiling git rev-list --count on a 2.5M-commit monorepo
> shows sift_down_root dropping from 8.2% to 0.4% of total runtime,
> effectively eliminated as significant overhead.
>
> Synthetic benchmark 10 rounds of 10M put+get cycles, CPU-pinned, median
> of 3 runs, same compiler and Makefile flags.
>
> Ascending keys (git's typical pattern -- parents have lower priority
> than children):
>
> queue width baseline patched speedup
> 10 4.32s 3.97s 1.09x
> 100 7.95s 6.49s 1.23x
> 1,000 11.30s 9.66s 1.17x
> 10,000 16.34s 14.15s 1.16x
> 100,000 21.43s 18.66s 1.15x
>
>
> Descending keys (worst case — last element always sinks to leaf in both
> approaches):
>
> queue width baseline patched speedup
> 10 4.84s 4.78s 1.01x
> 100 9.43s 9.20s 1.03x
> 1,000 15.28s 14.71s 1.04x
> 10,000 23.61s 23.49s 1.01x
> 100,000 29.16s 28.22s 1.03x
>
>
> No regressions in any scenario.
>
> End-to-end benchmarks
>
> All benchmarks use a benchmark setup of 1 warmup run followed by 10
> timed runs. Each configuration is built from the same source tree and
> tested on the same repo in alternating order.
>
> linux kernel (1.4M commits) — range v5.0..v6.0 (311K commits):
>
> Command baseline patched speedup
> rev-list --count v5.0..v6.0 455ms 440ms 1.04x
>
>
> I also ran it on git.git but did not see any performance diff at all,
> due to the size and narrow DAG.
>
> The improvement scales with DAG width: wider DAGs produce larger
> priority queues, amplifying the per-level savings. In small or narrow
> repositories the priority queues stay shallow and the sift-down cost is
> already negligible, so the change is not noticeable.
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2132%2Fspkrka%2Fcascade-sift-down-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2132/spkrka/cascade-sift-down-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/2132
>
> prio-queue.c | 22 ++++++++++++----------
> 1 file changed, 12 insertions(+), 10 deletions(-)
>
> diff --git a/prio-queue.c b/prio-queue.c
> index 9748528ce6..18005c43c4 100644
> --- a/prio-queue.c
> +++ b/prio-queue.c
> @@ -62,17 +62,21 @@ static void sift_down_root(struct prio_queue *queue)
> {
> size_t ix, child;
>
> - /* Push down the one at the root */
> - for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
> - child = ix * 2 + 1; /* left */
> + for (ix = 0; (child = ix * 2 + 1) < queue->nr; ix = child) {
> if (child + 1 < queue->nr &&
> compare(queue, child, child + 1) >= 0)
> child++; /* use right child */
> + queue->array[ix] = queue->array[child];
> + }
>
> - if (compare(queue, ix, child) <= 0)
> + /* Place queue->array[queue->nr] (left by caller) and sift up. */
> + queue->array[ix] = queue->array[queue->nr];
> + while (ix) {
> + size_t parent = (ix - 1) / 2;
> + if (compare(queue, parent, ix) <= 0)
> break;
> -
> - swap(queue, child, ix);
> + swap(queue, parent, ix);
> + ix = parent;
> }
> }
>
> @@ -89,7 +93,6 @@ void *prio_queue_get(struct prio_queue *queue)
> if (!--queue->nr)
> return result;
>
> - queue->array[0] = queue->array[queue->nr];
> sift_down_root(queue);
> return result;
> }
> @@ -111,8 +114,7 @@ void prio_queue_replace(struct prio_queue *queue, void *thing)
> queue->array[queue->nr - 1].ctr = queue->insertion_ctr++;
> queue->array[queue->nr - 1].data = thing;
> } else {
> - queue->array[0].ctr = queue->insertion_ctr++;
> - queue->array[0].data = thing;
> - sift_down_root(queue);
> + prio_queue_get(queue);
> + prio_queue_put(queue, thing);
> }
> }
>
> base-commit: c69baaf57ba26cf117c2b6793802877f19738b0d
^ permalink raw reply
* Re: [PATCH] describe: fix --exclude, --match with --contains and --all
From: Tuomas Ahola @ 2026-05-31 23:46 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Jacob Keller, git, Jacob Keller
In-Reply-To: <xmqqo6hwcves.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> wrote:
> It is curious that this fails in some but not all CI jobs, and even
> more curious that these failures look the same.
>
> e.g., https://github.com/git/git/actions/runs/26671595367/job/78615760984#step:4:1984
>
> +++ diff -u expect actual
> --- expect 2026-05-30 02:21:23
> +++ actual 2026-05-30 02:21:23
> @@ -1 +1 @@
> -branch_A
> +remotes/origin/remote_branch_A
> error: last command exited with $?=1
> not ok 70 - describe --contains --all --exclude
> #
> # echo "branch_A" >expect &&
> # tagged_commit=$(git rev-parse "refs/tags/A^0") &&
> # git describe --contains --all --exclude="A" --exclude="c" --exclude="test*" $tagged_commit >actual &&
> # test_cmp expect actual
>
> Rings any bell?
That's way out of my wheelhouse but this seems to fix the failure
for Alpine at least:
-----8<-----
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index d6594ada53..1776ffab46 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -416,7 +416,7 @@ static void name_tips(struct mem_pool *string_pool)
* Try to set better names first, so that worse ones spread
* less.
*/
- QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
+ STABLE_QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
for (i = 0; i < tip_table.nr; i++) {
struct tip_table_entry *e = &tip_table.table[i];
if (e->commit) {
^ permalink raw reply related
* [PATCH] Makefile: drop duplicate %.a from link recipes
From: Harald Nordgren via GitGitGadget @ 2026-05-31 23:16 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Three link recipes list archive files twice on the link line: once
via $(filter %.a,$^) and again through $(LIBS), which expands to
$(filter-out %.o,$(GITLIBS)) $(EXTLIBS). On macOS the linker warns
about the duplicates:
ld: warning: ignoring duplicate libraries: 'libgit.a', 'target/release/libgitcore.a'
Drop the redundant filter from the test-helper, fuzz-program, and
unit-test recipes so they match the pattern used by other link
recipes in the file.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
Makefile: drop duplicate %.a from test-helper link rule
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2314%2FHaraldNordgren%2Fmakefile-test-helper-dedup-libs-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2314/HaraldNordgren/makefile-test-helper-dedup-libs-v1
Pull-Request: https://github.com/git/git/pull/2314
Makefile | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Makefile b/Makefile
index b31ecb0756..309d1d1e74 100644
--- a/Makefile
+++ b/Makefile
@@ -3392,7 +3392,7 @@ perf: all
t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
- $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
@@ -4015,13 +4015,13 @@ fuzz-all: $(FUZZ_PROGRAMS)
$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
-Wl,--allow-multiple-definition \
- $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
+ $(filter %.o,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
$(GITLIBS) GIT-LDFLAGS
$(call mkdir_p_parent_template)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
- $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+ $(filter %.o,$^) $(LIBS)
GIT-TEST-SUITES: FORCE
@FLAGS='$(CLAR_TEST_SUITES)'; \
base-commit: 1666c1265231b0bc5f613fbbf3f0a9896cdef76e
--
gitgitgadget
^ permalink raw reply related
* [PATCH v3 12/12] git-gui: add gui and pick as explicit subcommands
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui accepts subcommands blame | browser | citool, and assumes the
subcommand is 'gui' if none is actually given, But, git-gui also has a
repository picker (choose_repository::pick) that can create a new
repository + worktree, or choose an existing one, switch to that, and
the run the gui. The user has no direct control over invoking the
picker, instead the picker is triggered by failure in the repository /
worktree discovery process: this includes being started in a directory
not controlled by git, which is probably the intended use case.
The picker can appear when the user has no intention of creating a new
worktree, and the user cannot use the picker to create a new worktree
inside another.
So, add two explicit subcommands:
gui - Run the gui if repository/worktree discovery succeeds, or die
with an error message, but never run the picker.
pick - First run the picker, regardless, then start the gui in
the chosen worktree.
Nothing in this changes the prior behavior, the alternates above must be
explicitly selected to see any change.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 22939215a6..933e72c9b2 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1024,6 +1024,8 @@ proc load_config {include_global} {
##
## feature option selection
+enable_option picker
+enable_option gitdir_discovery
if {[regexp {^git-(.+)$} [file tail $argv0] _junk subcommand]} {
unset _junk
} else {
@@ -1035,6 +1037,9 @@ if {$subcommand eq {gui.sh}} {
if {$subcommand eq {gui} && [llength $argv] > 0} {
set subcommand [lindex $argv 0]
set argv [lrange $argv 1 end]
+ if {$subcommand eq {gui}} {
+ disable_option picker
+ }
}
enable_option multicommit
@@ -1050,6 +1055,7 @@ blame {
disable_option multicommit
disable_option branch
disable_option transport
+ disable_option picker
}
citool {
enable_option singlecommit
@@ -1058,6 +1064,7 @@ citool {
disable_option multicommit
disable_option branch
disable_option transport
+ disable_option picker
while {[llength $argv] > 0} {
set a [lindex $argv 0]
@@ -1080,6 +1087,9 @@ citool {
set argv [lrange $argv 1 end]
}
}
+pick {
+ disable_option gitdir_discovery
+}
}
######################################################################
@@ -1168,7 +1178,7 @@ proc unset_gitdir_vars {} {
# find repository
set _gitdir {}
-if {$_gitdir eq {}} {
+if {[is_enabled gitdir_discovery]} {
if {[catch {
set _gitdir [git rev-parse --absolute-git-dir]
} err]} {
@@ -1180,7 +1190,7 @@ if {$_gitdir eq {}} {
}
set picked 0
-if {$_gitdir eq {}} {
+if {$_gitdir eq {} && [is_enabled picker]} {
unset_gitdir_vars
load_config 1
apply_config
@@ -1195,6 +1205,12 @@ if {$_gitdir eq {}} {
set picked 1
}
+if {$_gitdir eq {}} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Git directory not found:"] "\n\n$err"]
+ exit 1
+}
+
# find worktree, continue without if not required
if {[catch {
set _gitworktree [git rev-parse --show-toplevel]
@@ -3104,14 +3120,15 @@ blame {
return
}
citool -
-gui {
+gui -
+pick {
if {[llength $argv] != 0} {
usage
}
# fall through to setup UI for commits
}
default {
- set err "[mc usage:] $argv0 \[{blame|browser|citool}\]"
+ set err "[mc usage:] $argv0 \[{blame|browser|citool|gui|pick}\]"
if {[tk windowingsystem] eq "win32"} {
wm withdraw .
tk_messageBox -icon error -message $err \
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 11/12] git-gui: check browser/blame arguments carefully
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git gui offers two related commands, browser and blame, that provide
graphical interfaces driven by git ls-tree and git blame. As such, the
arguments to git-gui need to satisfy those two git commands. But,
git-gui does not assure this leading to confusing or incorrect results.
For instance 'git browser <non-existent path>' shows a blank browser
window rather than error message.
Also, commit 3e45ee1ef2 ("git-gui: Smarter command line parsing for
browser, blame", 2007-05-08) implemented code to allow giving path
before rev on the command line, and unconditionally uses the worktree to
disambiguate. As a result, the following command run in a current
git-gui checkout of the master branch shows the master branch version of
blame.tcl, when none should be shown as that file does not exist in
gitgui-0.6.0.
git gui blame lib/blame.tcl gitgui-0.6.0
This 'file before rev' feature in git-gui mirrors ideas considered when
git's user interface was very young, but no such feature is documented
for any git command. Rather than try to fix an idea git itself
rejected, let's just remove this broken and hopefully unused feature.
git-gui browser|blame both accept 'rev' and 'path' as command line
arguments. rev defaults to 'HEAD' if not given, while path must be
given. path names a directory tree to ls-tree or a file to blame. path
must exist in rev for ls-tree and for blame. In addition git blame will
include uncommitted changes from the worktree file at 'path' if rev is
not given (thus defaulting to HEAD), but still requires that the file
exists in HEAD.
So, let's clean up the parser to check that the arguments are usable.
- give a full synopsis, including '--' that may be used to separate rev and
path. (as path is the required final arg, -- gives no extra info)
- explicitly check the number of arguments
- use rev-parse to assure a user supplied rev is valid
- use ls-tree to assure that path exists in rev
- for blame only, with no rev given and a worktree existing, also assure
that path points to a file in the worktree
With these changes, error messages are thrown by the parser if the path
or rev are not known: no blank or erroneous displays are created. Also,
this avoids accessing the worktree except in the specific use case
supported by blame / git-blame, meaning browser|blame now also work
without a worktree.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 122 +++++++++++++++++++++++++++--------------------------
1 file changed, 63 insertions(+), 59 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 16d6b3051a..22939215a6 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -3000,101 +3000,105 @@ proc normalize_relpath {path} {
}
}
+proc show_parse_err {err} {
+ if {[tk windowingsystem] eq "win32"} {
+ catch {wm withdraw .}
+ error_popup $err
+ } else {
+ puts stderr $err
+ }
+ exit 1
+}
+
# -- Not a normal commit type invocation? Do that instead!
#
switch -- $subcommand {
browser -
blame {
if {$subcommand eq "blame"} {
- set subcommand_args {[--line=<num>] rev? path}
+ set subcommand_args {[--line=<num>] [rev] [--] <filename>}
+ set required_pathtype blob
} else {
- set subcommand_args {rev? path}
+ set subcommand_args {[rev] [--] <dirname>}
+ set required_pathtype tree
}
- if {$argv eq {}} usage
+ set maxargs [llength $subcommand_args]
+ set nargs [llength $argv]
+ if {$nargs < 1 || $nargs > $maxargs} usage
set head {}
set path {}
set jump_spec {}
- set is_path 0
- foreach a $argv {
- set p [file join $_prefix $a]
- if {$is_path || [file exists $p]} {
- if {$path ne {}} usage
- set path [normalize_relpath $p]
- break
+ set iarg 0
+ foreach a $argv {
+ incr iarg
+ if {$iarg == $nargs} {
+ # final argument is path
+ set path [normalize_relpath [file join $_prefix $a]]
} elseif {$a eq {--}} {
- if {$path ne {}} {
- if {$head ne {}} usage
- set head $path
- set path {}
+ # allow before required final arg that must be path
+ if {$iarg != $nargs - 1} {
+ usage
}
- set is_path 1
} elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
- if {$jump_spec ne {} || $head ne {}} usage
+ # --line can only be the first arg
+ if {$iarg != 1 || $subcommand ne {blame}} usage
set jump_spec [list $lnum]
} elseif {$head eq {}} {
- if {$head ne {}} usage
set head $a
- set is_path 1
} else {
usage
}
}
- unset is_path
- if {$head ne {} && $path eq {}} {
- if {[string index $head 0] eq {/}} {
- set path [normalize_relpath $head]
- set head {}
+ # If head not given, use current branch (HEAD),
+ # and blame will use worktree if there is one.
+ set use_worktree 0
+ if {$head eq {}} {
+ load_current_branch
+ set head $current_branch
+ if {$subcommand eq {blame} && ![is_bare]} {
+ if {![file isfile $path]} {
+ show_parse_err [mc "fatal: no such file '%s' in worktree" $path]
+ }
+ set use_worktree 1
+ }
+ } else {
+ if {[catch {
+ set commitid \
+ [git rev-parse --verify --end-of-options \
+ [strcat $head "^{commit}"]]
+ }]} {
+ show_parse_err [mc "fatal: '%s' is not a valid rev'" $head]
} else {
- set path [normalize_relpath $_prefix$head]
- set head {}
+ set current_branch $head
}
}
- if {$head eq {}} {
- load_current_branch
- } else {
- if {[regexp [string map "@@ [expr $hashlength - 1]" {^[0-9a-f]{1,@@}$}] $head]} {
- if {[catch {
- set head [git rev-parse --verify $head]
- } err]} {
- if {[tk windowingsystem] eq "win32"} {
- tk_messageBox -icon error -title [mc Error] -message $err
- } else {
- puts stderr $err
- }
- exit 1
- }
+ # check path is known in head, and is file / directory as required
+ set pathtype {}
+ catch {set pathtype [git ls-tree {--format=%(objecttype)} $head $path]}
+ if {$pathtype ne {} && $path eq {.}} {
+ # ls-tree gives contents of root-dir, we need root-dir itself
+ set pathtype {tree}
+ }
+
+ if {$pathtype ne $required_pathtype} {
+ switch -- $required_pathtype {
+ tree {show_parse_err \
+ [mc "'%s' is not a directory in rev '%s'" $path $head]}
+ blob {show_parse_err \
+ [mc "'%s' is not a filename in rev '%s'" $path $head]}
}
- set current_branch $head
}
wm deiconify .
switch -- $subcommand {
browser {
- if {$jump_spec ne {}} usage
- if {$head eq {}} {
- if {$path ne {} && [file isdirectory $path]} {
- set head $current_branch
- } else {
- set head $path
- set path {}
- }
- }
browser::new $head $path
}
blame {
- if {$head eq {} && ![file exists $path]} {
- catch {wm withdraw .}
- tk_messageBox \
- -icon error \
- -type ok \
- -title [mc "git-gui: fatal error"] \
- -message [mc "fatal: cannot stat path %s: No such file or directory" $path]
- exit 1
- }
- blame::new $head $path $jump_spec
+ blame::new [expr {$use_worktree ? {} : $head}] $path $jump_spec
}
}
return
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 10/12] git-gui: allow specifying path '.' to the browser
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
Invoking "git-gui browser rev ." should show the file browser for the
commitish rev, starting at the current directory. When the current
directory is the working tree root, this errors out in normalize_relpath
because the '.' is removed, yielding an empty list as argument to [file
join ...]. git ls-tree (underlying the browser) accepts '.', so use that
as the value when in the root.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/git-gui.sh b/git-gui.sh
index f48d929da8..16d6b3051a 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -2993,7 +2993,11 @@ proc normalize_relpath {path} {
}
lappend elements $item
}
- return [eval file join $elements]
+ if {$elements ne {}} {
+ return [eval file join $elements]
+ } else {
+ return {.}
+ }
}
# -- Not a normal commit type invocation? Do that instead!
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 09/12] git-gui: try harder to find worktree from gitdir
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui, since 87cd09f43e ("git-gui: work from the .git dir",
2010-01-23), has had the intent to allow starting from inside a
repository, then switching to the parent directory if that is a valid
worktree.
This certainly hasn't worked since 2d92ab32fd ("rev-parse: make
--show-toplevel without a worktree an error", 2019-11-19) in git, but
breaking this git-gui feature was unintentional.
There are (at least) 3 cases where the gitdir can tell us where the
worktree is, and we would like all to work:
- core.worktree is set, and points to a valid worktree. This is already
handled by git rev-parse --show-toplevel, even when not in the worktree.
There is nothing more to do in this case.
- the gitdir is embedded in a worktree as subdirectory .git. The parent
is (or at least should be) a valid worktree. This worked long ago.
- the gitdir is a worktree specific directory (under
<mainrepo>/worktrees/worktree_name), within which there is a file
"gitdir" pointing to .git in the worktree. git gui never learned to
handle this case.
Let's handle the latter two cases. Always check that the discovered
worktree is valid and points to the already discovered gitdir according
to git rev-parse. This avoids issues that may arise because we are
discovering from the gitdir up, rather than the worktree down, and file
system non-posix behavior or misconfiguration of git might cause
confusion. For instance, a manually moved worktree might not be where
the gitdir points, or the gitdir might be configured with
core.bare=true.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/git-gui.sh b/git-gui.sh
index abee222903..f48d929da8 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1103,6 +1103,37 @@ unset argv0dir
##
## repository setup
+proc find_worktree_from_gitdir {} {
+ # this is invoked only if the current directory is inside the repository
+ set worktree {}
+ if {[file tail $::_gitdir] eq {.git}} {
+ # the dir containing .git is a worktree if repo allows it
+ # Check that git reports parent as a worktree (gitdir might not allow a worktree)
+ if {[catch {
+ set parent [file dirname $::_gitdir]
+ set worktree [git -C $parent rev-parse --show-toplevel]
+ }]} {
+ set worktree {}
+ }
+ } elseif [file exists {gitdir}] {
+ # a worktree gitdir has .gitdir naming worktree/.git
+ # assure git run there reports this dir as the gitdir (links might be broken)
+ if {[catch {
+ set fd_gitdir [open {gitdir} {r}]
+ set worktree [file dirname [read $fd_gitdir]]
+ catch {close $fd_gitdir}
+ set worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir]
+ if {$::_gitdir ne $worktree_gitdir} {
+ set worktree {}
+ }
+ }]} {
+ catch {close $fd_gitdir}
+ set worktree {}
+ }
+ }
+ return $worktree
+}
+
proc is_gitvars_error {err} {
set havevars 0
set GIT_DIR {}
@@ -1176,6 +1207,13 @@ if {[catch {
set _prefix {}
}
+if {[is_bare]} {
+ # Maybe we are in an embedded or worktree specific gitdir
+ if {[set _gitworktree [find_worktree_from_gitdir]] ne {}} {
+ set _prefix {}
+ }
+}
+
if {![is_bare]} {
if {[catch {cd $_gitworktree} err]} {
catch {wm withdraw .}
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 08/12] git-gui: simplify [is_bare] to report if a worktree is known
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui includes proc is_bare, used in several places to make decisions
on whether a worktree exists, but also in discovery to tell if a
worktree can be supported.
But, is_bare is out of date with regard to multiple worktrees, safe
repository guards, and possibly other relevant features known to git
rev-parse. Also, is_bare caches its result on the first call, so is not
useful if a later step in the discovery process finds a worktree.
So, simplify is_bare to report whether git-gui has a worktree or is
working only from a repository.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 25 +------------------------
1 file changed, 1 insertion(+), 24 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 53b6859345..abee222903 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -372,7 +372,6 @@ if {[tk windowingsystem] eq "aqua"} {
set _appname {Git Gui}
set _gitdir {}
set _gitworktree {}
-set _isbare {}
set _githtmldir {}
set _prefix {}
set _reponame {}
@@ -524,29 +523,7 @@ proc get_config {name} {
}
proc is_bare {} {
- global _isbare
- global _gitdir
- global _gitworktree
-
- if {$_isbare eq {}} {
- if {[catch {
- set _bare [git rev-parse --is-bare-repository]
- switch -- $_bare {
- true { set _isbare 1 }
- false { set _isbare 0}
- default { throw }
- }
- }]} {
- if {[is_config_true core.bare]
- || ($_gitworktree eq {}
- && [lindex [file split $_gitdir] end] ne {.git})} {
- set _isbare 1
- } else {
- set _isbare 0
- }
- }
- }
- return $_isbare
+ return [expr {$::_gitworktree eq {}}]
}
######################################################################
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 07/12] git-gui: use git rev-parse for worktree discovery
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git gui uses a combination of tcl code and git invocations to determine
the worktree and the location with respect to the worktree root
(_prefix). But, git rev-parse provides all of this information directly,
and assures full error and configuration checking are done by git
itself. The entirety of discovery in normal configurations involves
git rev-parse --show-toplevel (gets worktree root)
git rev-parse --show-prefix (shows location wrt the root)
An error thrown on either of these lines means the worktree discovered
by git is unusable, or git did not discover a worktree because the
current directory is inside the repository. If the user has defined
GIT_DIR or GIT_WORK_TREE, this is a user configuration error and git-gui
should stop.
Otherwise, the blame or browser subcommands can be used without a
worktree.
A separate error might occur when changing to the root of the discovered
worktree. The cause would be file system related and completely outside
of git's control, so trap that independently.
Discovery of the repository and the worktree must be guarded to trap
errors: the intent is that any configuration problems are caught during
discovery, and later processing need not include error trapping and
recovery. So, move all worktree discovery code to be immediately after
repository discovery.
This does move configuration loading to occur after worktree discovery
rather than before. None of the code executed in worktree discovery has
any option controlled by a git-gui configuration variable, so no impact
is expected. git itself will always read the repository configuration,
including worktree specific configuration data if that exists, so this
is unaffected by when git-gui loads its own config data. Also, we cannot
be sure the worktree dependent configuration can be loaded before
full discovery is complete.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 61 ++++++++++++++++++++++++------------------------------
1 file changed, 27 insertions(+), 34 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index aed3bc7200..53b6859345 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1187,6 +1187,33 @@ if {$_gitdir eq {}} {
set picked 1
}
+# find worktree, continue without if not required
+if {[catch {
+ set _gitworktree [git rev-parse --show-toplevel]
+ set _prefix [git rev-parse --show-prefix]
+ } err]} {
+ if {[is_gitvars_error $err]} {
+ exit 1
+ }
+ set _gitworktree {}
+ set _prefix {}
+}
+
+if {![is_bare]} {
+ if {[catch {cd $_gitworktree} err]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
+ exit 1
+ }
+} elseif {![is_enabled bare]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
+ exit 1
+}
+
+# repository and worktree config are complete, export them
+set_gitdir_vars
+
# Use object format as hash algorithm (either "sha1" or "sha256")
set hashalgorithm [git rev-parse --show-object-format]
if {$hashalgorithm eq "sha1"} {
@@ -1202,37 +1229,6 @@ if {$hashalgorithm eq "sha1"} {
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]} {
- catch {wm withdraw .}
- error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
- 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
- }
- 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"]
- exit 1
- }
- set _gitworktree [pwd]
-}
set _reponame [file split [file normalize $_gitdir]]
if {[lindex $_reponame end] eq {.git}} {
set _reponame [lindex $_reponame end-1]
@@ -1240,9 +1236,6 @@ if {[lindex $_reponame end] eq {.git}} {
set _reponame [lindex $_reponame end]
}
-# Export the final paths
-set_gitdir_vars
-
######################################################################
##
## global init
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 06/12] git-gui: use rev-parse exclusively to find a repository
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui attempts to use env(GIT_DIR) directly as the git repository,
accepting GIT_DIR if it is a directory. Only if that fails is git
rev-parse used to discover the repository. But, this avoids all of
git-core's validity checking on a repository, thus possibly deferring an
error to a later step, possibly unexpected. Repository validation should
be part of initial setup so that later processing does not need error
trapping for configuration errors.
Let's just invoke rev-parse so all error checking is done.
While here, let's cleanup the error handling.
Stop if an error occurs and the user set GIT_DIR or GIT_WORK_TREE.
Use of either or both of those variables is supported by git, but their
use also means the user has taken responsibility that they are correct,
so a failure is something the user must address.
Otherwise on error, continue the existing behavior and show the
repository picker. But, let's move the possible invocation of
repository_chooser::pick to a separate code block. This permits adding
separate conditions on using pick independent of repository discovery, and
will be exploited later in the series. Note that the picker always
returns with the current directory in the root of a worktree with the
git repository is in the .git subdirectory. The variable "picked" is
used by git-gui to automatically execute the "Explore Working Copy" menu
item after the repository picker is run. This is controlled by config
variable gui.autoexplore, and happens after all discovery is complete.
Remove a later check on whether _gitdir is a directory: that code
cannot be reached without rev-parse already validating the repository.
_prefix is set as part of worktree discovery, but must be {} if not
running with a worktree. Initialze this as {} along with other global
variables, this is the correct value is no worktree is found.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 49 ++++++++++++++++++++++++++++++++++---------------
1 file changed, 34 insertions(+), 15 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 8a5f0bd938..aed3bc7200 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -374,6 +374,7 @@ set _gitdir {}
set _gitworktree {}
set _isbare {}
set _githtmldir {}
+set _prefix {}
set _reponame {}
set _shellpath {@@SHELL_PATH@@}
@@ -1125,6 +1126,24 @@ unset argv0dir
##
## repository setup
+proc is_gitvars_error {err} {
+ set havevars 0
+ set GIT_DIR {}
+ set GIT_WORK_TREE {}
+ catch {set GIT_DIR $::env(GIT_DIR); set havevars 1}
+ catch {set GIT_WORK_TREE $::env(GIT_WORK_TREE); set havevars 1}
+
+ if {$havevars} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Invalid configuration:"] \
+ "\n" "GIT_DIR: " $GIT_DIR \
+ "\n" "GIT_WORK_TREE: " $GIT_WORK_TREE \
+ "\n\n$err"]
+ return 1
+ }
+ return 0
+}
+
proc set_gitdir_vars {} {
global _gitdir _gitworktree env
set env(GIT_DIR) $_gitdir
@@ -1139,16 +1158,22 @@ proc unset_gitdir_vars {} {
catch {unset env(GIT_WORK_TREE)}
}
+# find repository
+set _gitdir {}
+if {$_gitdir eq {}} {
+ if {[catch {
+ set _gitdir [git rev-parse --absolute-git-dir]
+ } err]} {
+ if {[is_gitvars_error $err]} {
+ exit 1
+ }
+ set _gitdir {}
+ }
+}
+
set picked 0
-if {[catch {
- set _gitdir $env(GIT_DIR)
- set _prefix {}
- }]
- && [catch {
- # beware that from the .git dir this sets _prefix to the empty string
- set _gitdir [git rev-parse --absolute-git-dir]
- set _prefix [git rev-parse --show-prefix]
- } err]} {
+if {$_gitdir eq {}} {
+ unset_gitdir_vars
load_config 1
apply_config
choose_repository::pick
@@ -1159,7 +1184,6 @@ if {[catch {
error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] \n\n$err"]
exit 1
}
- set _prefix {}
set picked 1
}
@@ -1174,11 +1198,6 @@ if {$hashalgorithm eq "sha1"} {
exit 1
}
-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
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 05/12] git-gui: use --absolute-git-dir
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui uses git rev-parse --git-dir to get the pathname of the
discovered git repository. The returned value can be relative, and is
'.' if the current directory is the top of the repository directory
itself. git-gui has code to change '.' to [pwd] in this case so that
subsequent logic runs.
But, git rev-parse supports --absolute-git-dir from fac60b8925
("rev-parse: add option for absolute or relative path formatting",
2020-12-13), and included in git 2.31. git-gui requires git >= 2.36, so
this more useful form is always available. Use --absolute-git-dir to
always get an absolute path, avoiding the need for other checks, and
delete the now unneeded code to fix a relative _gitdir.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 44914bddcf..8a5f0bd938 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1145,16 +1145,15 @@ if {[catch {
set _prefix {}
}]
&& [catch {
- # beware that from the .git dir this sets _gitdir to .
- # and _prefix to the empty string
- set _gitdir [git rev-parse --git-dir]
+ # beware that from the .git dir this sets _prefix to the empty string
+ set _gitdir [git rev-parse --absolute-git-dir]
set _prefix [git rev-parse --show-prefix]
} err]} {
load_config 1
apply_config
choose_repository::pick
if {[catch {
- set _gitdir [git rev-parse --git-dir]
+ set _gitdir [git rev-parse --absolute-git-dir]
} err]} {
catch {wm withdraw .}
error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] \n\n$err"]
@@ -1175,13 +1174,6 @@ if {$hashalgorithm eq "sha1"} {
exit 1
}
-# we expand the _gitdir when it's just a single dot (i.e. when we're being
-# run from the .git dir itself) lest the routines to find the worktree
-# get confused
-if {$_gitdir eq "."} {
- set _gitdir [pwd]
-}
-
if {![file isdirectory $_gitdir]} {
catch {wm withdraw .}
error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 04/12] git-gui: do not change global vars in choose_repository::pick
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
The repository picker (choose_repository::pick, AKA pick) on success
always returns with the current directory at the root of the selected
worktree, with the global variable _gitdir holding the name of the
git repository, possibly as a relative path, and _prefix {}. The
worktree root (_gitworktree) is not filled out, and if the selection was
from the "recent" list, no validation has occurred beyond testing that
the worktree root exists. So, repository and worktree validation are
still needed to be sure the new repo + worktree is usable.
pick only supports worktrees with a .git entry in the worktree root, so
git repository and worktree discovery will work starting in the current
directory on return. In cases of error, or user abort, pick exits the
process rather than returning.
So, let's change pick to not alter any global values, with success
indicated by the process returning to the caller. In this case, the
current directory is the worktree root, with a .git entry. The caller
then proceeds with normal discovery to find and validate both repository
and worktree.
With this, pick now returns 1 in the success case, but additional work
would be necessary to return from conditions where 0 should be returned.
Checking this return value would be superfluous.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 7 ++++++-
lib/choose_repository.tcl | 21 ++++++++-------------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 2cf14d0dd5..44914bddcf 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1153,9 +1153,14 @@ if {[catch {
load_config 1
apply_config
choose_repository::pick
- if {![file isdirectory $_gitdir]} {
+ if {[catch {
+ set _gitdir [git rev-parse --git-dir]
+ } err]} {
+ catch {wm withdraw .}
+ error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] \n\n$err"]
exit 1
}
+ set _prefix {}
set picked 1
}
diff --git a/lib/choose_repository.tcl b/lib/choose_repository.tcl
index 7e1462a20c..4b06afee93 100644
--- a/lib/choose_repository.tcl
+++ b/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 pick_ok 0 ; # true if repo pick/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'
@@ -220,6 +220,8 @@ constructor pick {} {
if {$top eq {.}} {
eval destroy [winfo children $top]
}
+
+ return $pick_ok
}
method _center {} {
@@ -327,8 +329,7 @@ method _git_init {} {
}
_append_recentrepos [pwd]
- set ::_gitdir .git
- set ::_prefix {}
+ set pick_ok 1
return 1
}
@@ -409,6 +410,7 @@ method _do_new2 {} {
if {![_git_init $this]} {
return
}
+ set pick_ok 1
set done 1
}
@@ -621,7 +623,7 @@ method _do_clone2 {} {
}
tkwait variable @done
- if {!$clone_ok} {
+ if {!$pick_ok} {
error_popup [mc "Clone failed."]
return
}
@@ -632,18 +634,12 @@ method _do_clone2_done {ok} {
if {$ok} {
if {[catch {
cd $local_path
- set ::_gitdir .git
- set ::_prefix {}
_append_recentrepos [pwd]
} err]} {
set ok 0
}
}
- if {!$ok} {
- set ::_gitdir {}
- set ::_prefix {}
- }
- set clone_ok $ok
+ set pick_ok $ok
set done 1
}
@@ -721,8 +717,7 @@ method _do_open2 {} {
}
_append_recentrepos [pwd]
- set ::_gitdir $actualgit
- set ::_prefix {}
+ set pick_ok 1
set done 1
}
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 03/12] git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
git-gui unconditionally exports _gitdir as GIT_DIR, and _gitworktree as
GIT_WORK_TREE, to the environment, and unconditionally
unsets these environment variables before invoking gitk or git-gui when
a submodule is involved. This export happens even if _gitworktree is
empty, which happens when running from a bare repository. However,
exporting GIT_WORK_TREE as empty is never valid, and causes errors in
git.
GIT_DIR must be exported if the repository is not discoverable from the
worktree (or current directory if there is no worktree). The user might
have configured this.
If there is a worktree, git-gui makes this the current directory.
However, if the repository sets core.worktree, this value can only be
overridden by GIT_WORK_TREE so the latter must be exported.
As we cannot eliminate conditions where either variable is needed, let's
implement a pair of functions to set / unset these variables without
error, and without ever exporting an empty GIT_WORK_TREE.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 37 +++++++++++++++++++++----------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index 52897fbd09..2cf14d0dd5 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1125,6 +1125,20 @@ unset argv0dir
##
## repository setup
+proc set_gitdir_vars {} {
+ global _gitdir _gitworktree env
+ set env(GIT_DIR) $_gitdir
+ if {$_gitworktree ne {}} {
+ set env(GIT_WORK_TREE) $_gitworktree
+ }
+}
+
+proc unset_gitdir_vars {} {
+ global env
+ catch {unset env(GIT_DIR)}
+ catch {unset env(GIT_WORK_TREE)}
+}
+
set picked 0
if {[catch {
set _gitdir $env(GIT_DIR)
@@ -1210,8 +1224,8 @@ if {[lindex $_reponame end] eq {.git}} {
set _reponame [lindex $_reponame end]
}
-set env(GIT_DIR) $_gitdir
-set env(GIT_WORK_TREE) $_gitworktree
+# Export the final paths
+set_gitdir_vars
######################################################################
##
@@ -2010,7 +2024,7 @@ proc incr_font_size {font {amt 1}} {
proc do_gitk {revs {is_submodule false}} {
global current_diff_path file_states current_diff_side ui_index
- global _gitdir _gitworktree
+ global _gitworktree
# -- Always start gitk through whatever we were loaded with. This
# lets us bypass using shell process on Windows systems.
@@ -2020,8 +2034,6 @@ proc do_gitk {revs {is_submodule false}} {
if {$exe eq {}} {
error_popup [mc "Couldn't find gitk in PATH"]
} else {
- global env
-
set pwd [pwd]
if {$is_submodule} {
@@ -2049,13 +2061,11 @@ proc do_gitk {revs {is_submodule false}} {
# TODO we could make life easier (start up faster?) for gitk
# by setting these to the appropriate values to allow gitk
# to skip the heuristics to find their proper value
- unset env(GIT_DIR)
- unset env(GIT_WORK_TREE)
+ unset_gitdir_vars
}
safe_exec_bg [concat $cmd $revs "--" "--"]
- set env(GIT_DIR) $_gitdir
- set env(GIT_WORK_TREE) $_gitworktree
+ set_gitdir_vars
cd $pwd
if {[info exists main_status]} {
@@ -2078,21 +2088,16 @@ proc do_git_gui {} {
if {$exe eq {}} {
error_popup [mc "Couldn't find git gui in PATH"]
} else {
- global env
- global _gitdir _gitworktree
-
# see note in do_gitk about unsetting these vars when
# running tools in a submodule
- unset env(GIT_DIR)
- unset env(GIT_WORK_TREE)
+ unset_gitdir_vars
set pwd [pwd]
cd $current_diff_path
safe_exec_bg [concat $exe gui]
- set env(GIT_DIR) $_gitdir
- set env(GIT_WORK_TREE) $_gitworktree
+ set_gitdir_vars
cd $pwd
set status_operation [$::main_status \
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 02/12] git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
From: Johannes Sixt <j6t@kdbg.org>
In the procedure that invokes Gitk, we have a 'cd $_gitworktree'. Such
a change of the current directory is not necessary, because
- if we have a working tree, then the startup routine has already
changed the current directory to the root of the working tree, which
*is* $_gitworktree; or
- if we are in a bare repository, then there is no point in changing
the current directory anywhere. (And $_gitworktree is empty.)
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/git-gui.sh b/git-gui.sh
index f70a54a61b..52897fbd09 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -2024,11 +2024,7 @@ proc do_gitk {revs {is_submodule false}} {
set pwd [pwd]
- if {!$is_submodule} {
- if {![is_bare]} {
- cd $_gitworktree
- }
- } else {
+ if {$is_submodule} {
cd $current_diff_path
if {$revs eq {--}} {
set s $file_states($current_diff_path)
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 01/12] git-gui: use HEAD as current branch when detached
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260531230225.126817-1-mlevedahl@gmail.com>
commit f87a36b697 ("git-gui: use git-branch --show-current", 2024-02-12)
changed git-gui to use git-branch to access refs, rather than directly
reading files as doing the latter is not compatible with the reftable
backend. git branch --show-current reports an empty branch name when the
head is detached, and in this case load_current_branch needs to report
HEAD using special case logic as it did prior to the above commit. Make
it do so.
This addresses an issue with git-gui browser failing with a detached
head.
Signed-off-by: Mark Levedahl <mlevedahl@gmail.com>
---
git-gui.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/git-gui.sh b/git-gui.sh
index 23fe76e498..f70a54a61b 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -670,6 +670,9 @@ proc load_current_branch {} {
set current_branch [git branch --show-current]
set is_detached [expr [string length $current_branch] == 0]
+ if {$is_detached} {
+ set current_branch {HEAD}
+ }
}
auto_load tk_optionMenu
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v3 00/12] Improve git gui operation without a worktree
From: Mark Levedahl @ 2026-05-31 23:02 UTC (permalink / raw)
To: git; +Cc: j6t, egg_mushroomcow, bootaina702, Mark Levedahl
In-Reply-To: <20260520202411.108764-1-mlevedahl@gmail.com>
git gui has a number of inter-related problems that result in problems
during startup from anything but a checked out worktree pointing at a
valid git repository. Some of the symptoms are:
- blame / browser subcommands, and launching gitk, are intended to be
useful without a worktree, but fail to work.
- unlike git, git-gui is supposed to use the parent directory as a
worktree if started from the .git subdirectory in the very common
single worktree + embedded git repository format. This does not
work.
- git-gui includes a repository picker allowing a user to select a
worktree from a list and/or start a new repo+worktree: this dialog can
appear at unexpected times, masking useful error feedback on
configuration problems.
This patch series addresses the above issues, substantially rewriting
the initial repository/worktree process to rely upon git rev-parse so
that git's knowledge of access rules, repository configuration, and use
of GIT_DIR / GIT_WORK_TREE (or git --gitdir / --work-tree) is used
throughout, replacing code largely based upon what git did in 2008. This
also means that git gui will naturally gain any new rules implmented in
git-core.
With this, git-gui only exports GIT_WORK_TREE when non-empty.
GIT_WORK_TREE is needed, and must be exported, if the user is overriding
core.worktree in the git repository. But, GIT_WORK_TREE cannot be used
to specify the lack of a worktree, so exporting an empty GIT_WORK_TREE
is one of the problems fixed by this series.
v3 of this series addresses j6t's review of v2, with some reordering of
patches (1 from j6t added, patch #8 moved to #1), adds another rewrite
of the browser / blame parser that eliminates the notion of path before
rev on the command line, blame works correctly with a user modified file
in the worktree. Clarification is added on the need for GTI_WORK_TREE,
and the logic in finding a worktree from the gitdir is simplified.
Johannes Sixt (1):
git-gui: remove unnecessary 'cd $_gitworktree' from do_gitk
Mark Levedahl (11):
git-gui: use HEAD as current branch when detached
git-gui: guard set/unset of GIT_DIR and GIT_WORK_TREE
git-gui: do not change global vars in choose_repository::pick
git-gui: use --absolute-git-dir
git-gui: use rev-parse exclusively to find a repository
git-gui: use git rev-parse for worktree discovery
git-gui: simplify [is_bare] to report if a worktree is known
git-gui: try harder to find worktree from gitdir
git-gui: allow specifying path '.' to the browser
git-gui: check browser/blame arguments carefully
git-gui: add gui and pick as explicit subcommands
git-gui.sh | 377 ++++++++++++++++++++++----------------
lib/choose_repository.tcl | 21 +--
2 files changed, 223 insertions(+), 175 deletions(-)
Interdiff against v2:
diff --git a/git-gui.sh b/git-gui.sh
index 299c1a0292..933e72c9b2 100755
--- a/git-gui.sh
+++ b/git-gui.sh
@@ -1114,33 +1114,29 @@ unset argv0dir
## repository setup
proc find_worktree_from_gitdir {} {
- # Directory 'parent' of a repository named 'parent/.git' might be the worktree.
- # Assure parent is a worktree and using the git repository already discovered.
- # Also, handle case of being in a worktree's gitdir, where file "gitdir" points to
- # gitlink file .git in the real worktree.
+ # this is invoked only if the current directory is inside the repository
set worktree {}
if {[file tail $::_gitdir] eq {.git}} {
+ # the dir containing .git is a worktree if repo allows it
+ # Check that git reports parent as a worktree (gitdir might not allow a worktree)
if {[catch {
- set gitdir_parent [file dirname $::_gitdir]
- set worktree [git -C $gitdir_parent rev-parse --show-toplevel]
- set parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]
- if {$::_gitdir ne $parent_gitdir} {
- set worktree {}
- }
- }]} {
+ set parent [file dirname $::_gitdir]
+ set worktree [git -C $parent rev-parse --show-toplevel]
+ }]} {
set worktree {}
}
} elseif [file exists {gitdir}] {
+ # a worktree gitdir has .gitdir naming worktree/.git
+ # assure git run there reports this dir as the gitdir (links might be broken)
if {[catch {
- set fd_gitdir [open {gitdir} {r}]
- set gitlink_parent [file dirname [read $fd_gitdir]]
- catch {close $fd_gitdir}
- set worktree [git -C $gitlink_parent rev-parse --show-toplevel]
- set parent_gitdir [git -C $worktree rev-parse --absolute-git-dir]
- if {$::_gitdir ne $parent_gitdir} {
- set worktree {}
- }
- }]} {
+ set fd_gitdir [open {gitdir} {r}]
+ set worktree [file dirname [read $fd_gitdir]]
+ catch {close $fd_gitdir}
+ set worktree_gitdir [git -C $worktree rev-parse --absolute-git-dir]
+ if {$::_gitdir ne $worktree_gitdir} {
+ set worktree {}
+ }
+ }]} {
catch {close $fd_gitdir}
set worktree {}
}
@@ -1153,7 +1149,7 @@ proc is_gitvars_error {err} {
set GIT_DIR {}
set GIT_WORK_TREE {}
catch {set GIT_DIR $::env(GIT_DIR); set havevars 1}
- catch {set GIT_WORK_TREE $::env(GIT_WORK_TREE) ; set havevars 1}
+ catch {set GIT_WORK_TREE $::env(GIT_WORK_TREE); set havevars 1}
if {$havevars} {
catch {wm withdraw .}
@@ -1168,9 +1164,7 @@ proc is_gitvars_error {err} {
proc set_gitdir_vars {} {
global _gitdir _gitworktree env
- if {$_gitdir ne {}} {
- set env(GIT_DIR) $_gitdir
- }
+ set env(GIT_DIR) $_gitdir
if {$_gitworktree ne {}} {
set env(GIT_WORK_TREE) $_gitworktree
}
@@ -1182,12 +1176,12 @@ proc unset_gitdir_vars {} {
catch {unset env(GIT_WORK_TREE)}
}
-# find repository.
+# find repository
set _gitdir {}
if {[is_enabled gitdir_discovery]} {
if {[catch {
- set _gitdir [git rev-parse --absolute-git-dir]
- } err]} {
+ set _gitdir [git rev-parse --absolute-git-dir]
+ } err]} {
if {[is_gitvars_error $err]} {
exit 1
}
@@ -1200,14 +1194,13 @@ if {$_gitdir eq {} && [is_enabled picker]} {
unset_gitdir_vars
load_config 1
apply_config
- if {![choose_repository::pick]} {
- exit 1
- }
+ choose_repository::pick
if {[catch {
- set _gitdir [git rev-parse --absolute-git-dir]
- } err]} {
+ set _gitdir [git rev-parse --absolute-git-dir]
+ } err]} {
catch {wm withdraw .}
- error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] "\n\n$err"]
+ error_popup [strcat [mc "Unusable repo/worktree:"] " [pwd] \n\n$err"]
+ exit 1
}
set picked 1
}
@@ -1220,9 +1213,9 @@ if {$_gitdir eq {}} {
# find worktree, continue without if not required
if {[catch {
- set _gitworktree [git rev-parse --show-toplevel]
- set _prefix [git rev-parse --show-prefix]
-} err]} {
+ set _gitworktree [git rev-parse --show-toplevel]
+ set _prefix [git rev-parse --show-prefix]
+ } err]} {
if {[is_gitvars_error $err]} {
exit 1
}
@@ -1238,17 +1231,14 @@ if {[is_bare]} {
}
if {![is_bare]} {
- if {[catch {
- cd $_gitworktree
- } err]} {
+ if {[catch {cd $_gitworktree} err]} {
catch {wm withdraw .}
- error_popup [strcat [mc "Cannot change to discovered worktree: "] \
- "$_gitworktree" "\n\n$err"]
- exit 1;
+ error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
+ exit 1
}
} elseif {![is_enabled bare]} {
catch {wm withdraw .}
- error_popup [strcat [mc "Cannot use bare repository:"] "\n\n" $_gitdir]
+ error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
exit 1
}
@@ -2086,11 +2076,7 @@ proc do_gitk {revs {is_submodule false}} {
} else {
set pwd [pwd]
- if {!$is_submodule} {
- if {![is_bare]} {
- cd $_gitworktree
- }
- } else {
+ if {$is_submodule} {
cd $current_diff_path
if {$revs eq {--}} {
set s $file_states($current_diff_path)
@@ -3026,26 +3012,18 @@ proc normalize_relpath {path} {
if {$elements ne {}} {
return [eval file join $elements]
} else {
- return {./}
+ return {.}
}
}
-proc find_path_type {head path} {
- if {$path eq {./}} {
- # the root-tree exists in every rev, ls-tree gives data on the contents,
- # not the type of tree itself. So, if the rev exists, return {tree}
- if {[catch {set objtype [git ls-tree $head]}]} {
- set objtype {}
- } else {
- set objtype {tree}
- }
+proc show_parse_err {err} {
+ if {[tk windowingsystem] eq "win32"} {
+ catch {wm withdraw .}
+ error_popup $err
} else {
- # test that the path exists in head, ls-tree gives info on the path only
- if {[catch {set objtype [git ls-tree {--format=%(objecttype)} $head $path]}]} {
- set objtype {}
- }
+ puts stderr $err
}
- return $objtype
+ exit 1
}
# -- Not a normal commit type invocation? Do that instead!
@@ -3054,91 +3032,80 @@ switch -- $subcommand {
browser -
blame {
if {$subcommand eq "blame"} {
- set subcommand_args {[--line=<num>] <[rev] [--] filename | [--] filename rev>}
- set required_objtype blob
+ set subcommand_args {[--line=<num>] [rev] [--] <filename>}
+ set required_pathtype blob
} else {
- set subcommand_args {<[rev] [--] directory | [--] directory rev>}
- set required_objtype tree
+ set subcommand_args {[rev] [--] <dirname>}
+ set required_pathtype tree
}
set maxargs [llength $subcommand_args]
set nargs [llength $argv]
if {$nargs < 1 || $nargs > $maxargs} usage
set head {}
- set althead {}
set path {}
- set altpath {}
- set canswap 1
set jump_spec {}
- # assume: [--line=num] [head] [--] path as the possible arguments, in order.
- # head and path may need a swap later.
- for {set iarg 0} {$iarg < $nargs} {incr iarg} {
- set arg [lindex $argv $iarg]
- if {$arg eq {--}} {
- # next arg is the path, prevent or FORCE swap?
- if {$iarg == $nargs - 2} {
- set canswap 0
- } elseif {$iarg == $nargs - 3} {
- set canswap 2
- } else {
+ set iarg 0
+ foreach a $argv {
+ incr iarg
+ if {$iarg == $nargs} {
+ # final argument is path
+ set path [normalize_relpath [file join $_prefix $a]]
+ } elseif {$a eq {--}} {
+ # allow before required final arg that must be path
+ if {$iarg != $nargs - 1} {
usage
}
- } elseif {[regexp {^--line=(\d+)$} $arg arg lnum]} {
+ } elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
# --line can only be the first arg
- if {$iarg != 0 || $maxargs < 4} usage
+ if {$iarg != 1 || $subcommand ne {blame}} usage
set jump_spec [list $lnum]
- } elseif {$iarg == $nargs - 1} {
- # assume final argument is path
- set path [normalize_relpath [file join $_prefix $arg]]
- set althead $arg
} elseif {$head eq {}} {
- # assume the other argument is head
- set head $arg
- set altpath [normalize_relpath [file join $_prefix $arg]]
+ set head $a
} else {
usage
}
}
- # no swapping allowed if head not given, use current branch (HEAD)
+ # If head not given, use current branch (HEAD),
+ # and blame will use worktree if there is one.
+ set use_worktree 0
if {$head eq {}} {
load_current_branch
set head $current_branch
- set canswap 0
- }
-
- # -- before "rev" arg means we got -- path head
- if {$canswap == 2} {
- set head $althead
- set path $altpath
- set canswap 0
- }
-
- set objtype [find_path_type $head $path]
- if {$objtype eq {} && $canswap} {
- set objtype [find_path_type $althead $altpath]
- if {$objtype ne {}} {
- set head $althead
- set path $altpath
+ if {$subcommand eq {blame} && ![is_bare]} {
+ if {![file isfile $path]} {
+ show_parse_err [mc "fatal: no such file '%s' in worktree" $path]
+ }
+ set use_worktree 1
}
- }
- set current_branch $head
-
- # check that path exists in head, and objtype matches need
- if {$objtype ne $required_objtype} {
- switch -- $required_objtype {
- tree {set err [strcat \
- [mc "'%s' is not a directory in rev '%s'" $path $head]]}
- blob {set err [strcat \
- [mc "'%s' is not a filename in rev '%s'" $path $head]]}
- }
- if {[tk windowingsystem] eq "win32"} {
- catch {wm withdraw .}
- error_popup $err
+ } else {
+ if {[catch {
+ set commitid \
+ [git rev-parse --verify --end-of-options \
+ [strcat $head "^{commit}"]]
+ }]} {
+ show_parse_err [mc "fatal: '%s' is not a valid rev'" $head]
} else {
- puts stderr $err
+ set current_branch $head
+ }
+ }
+
+ # check path is known in head, and is file / directory as required
+ set pathtype {}
+ catch {set pathtype [git ls-tree {--format=%(objecttype)} $head $path]}
+ if {$pathtype ne {} && $path eq {.}} {
+ # ls-tree gives contents of root-dir, we need root-dir itself
+ set pathtype {tree}
+ }
+
+ if {$pathtype ne $required_pathtype} {
+ switch -- $required_pathtype {
+ tree {show_parse_err \
+ [mc "'%s' is not a directory in rev '%s'" $path $head]}
+ blob {show_parse_err \
+ [mc "'%s' is not a filename in rev '%s'" $path $head]}
}
- exit 1
}
wm deiconify .
@@ -3146,8 +3113,8 @@ blame {
browser {
browser::new $head $path
}
- blame {
- blame::new $head $path $jump_spec
+ blame {
+ blame::new [expr {$use_worktree ? {} : $head}] $path $jump_spec
}
}
return
--
2.54.0.99.14
^ permalink raw reply related
* [PATCH v2] doc: fix typos via codespell
From: Andrew Kreimer @ 2026-05-31 18:43 UTC (permalink / raw)
To: git; +Cc: Andrew Kreimer
In-Reply-To: <20260506101631.18127-1-algonell@gmail.com>
There are some typos in the documentation, comments, etc.
Fix them via codespell.
Signed-off-by: Andrew Kreimer <algonell@gmail.com>
---
v2:
- Drop typos under po/ and git-gui/ (different projects).
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 +-
hook.h | 2 +-
meson_options.txt | 2 +-
midx-write.c | 2 +-
odb/source.h | 2 +-
packfile.h | 2 +-
path.h | 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 +-
24 files changed, 40 insertions(+), 40 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 480cc0bd8c..558cf821fe 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1349,7 +1349,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/hook.h b/hook.h
index b4372b636f..27bb1aeb2e 100644
--- a/hook.h
+++ b/hook.h
@@ -128,7 +128,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 80a8025f20..d936ada098 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 561e9eedc0..19e1cd10b7 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -1461,7 +1461,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 0a440884e4..2f51fcb9ff 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -341,7 +341,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 49d6bdecf6..5729a37018 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/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 e009585925..62d3d2604c 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?
Interdiff against v1:
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 40e95bccb4..23fe76e498 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 occurrence of any duplicates.
+ # Keep only the first occurence 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 a4703af028..7e1462a20c 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 succeeded
+field clone_ok false ; # clone succeeeded
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 f4cffeac66..c18e201d85 100644
--- a/git-gui/lib/themed.tcl
+++ b/git-gui/lib/themed.tcl
@@ -4,7 +4,7 @@
namespace eval color {
# Variable colors
- # Preferred way to set widget colors is using add_option.
+ # Preffered 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/po/el.po b/po/el.po
index c45560c996..703f46d0c7 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 / Syncing Repositories"
+msgid "Low-level Commands / Synching Repositories"
msgstr "Εντολές Χαμηλού Επιπέδου / Συγχρονισμού Αποθετηρίων"
#: help.c:38
diff --git a/po/ko.po b/po/ko.po
index 6bc20a43e3..7a6847f023 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 / Syncing Repositories"
+msgid "Low-level Commands / Synching Repositories"
msgstr "보조 명령 / 저장소 동기화 기능"
#: help.c:38
Range-diff against v1:
1: 381e2c1fc3 ! 1: 674edefc99 doc: fix typos via codespell
@@ compat/precompose_utf8.c: const char *precompose_string_if_needed(const char *in
in = out;
}
- ## git-gui/git-gui.sh ##
-@@ git-gui/git-gui.sh: 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
- }
-
- ## git-gui/lib/choose_repository.tcl ##
-@@ git-gui/lib/choose_repository.tcl: 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'
-
- ## git-gui/lib/themed.tcl ##
-@@
-
- 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
-
## hook.h ##
@@ hook.h: struct run_hooks_opt {
* While the callback allows piecemeal writing, it can also be
@@ path.h: void safe_create_dir(struct repository *repo, const char *dir, int share
int safe_create_dir_in_gitdir(struct repository *repo, const char *path);
- ## po/el.po ##
-@@ po/el.po: 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
-
- ## po/ko.po ##
-@@ po/ko.po: 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
-
## reftable/system.h ##
@@ reftable/system.h: struct reftable_flock {
* to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative
--
2.54.0
^ permalink raw reply related
* [PATCH] prio-queue: use cascade-down sift for faster extract-min
From: Kristofer Karlsson via GitGitGadget @ 2026-05-31 17:57 UTC (permalink / raw)
To: git; +Cc: Kristofer Karlsson, Kristofer Karlsson
From: Kristofer Karlsson <krka@spotify.com>
Replace the standard sift-down in prio_queue_get() with a
cascade-down approach.
The standard approach places the last array element at the root,
then sifts it down. At each level this requires two comparisons
(left vs right child, then element vs winner) and, when the
element is larger, a swap (three 16-byte copies).
The cascade approach instead promotes the smaller child into the
vacant root slot at each level — one comparison and one copy.
The vacancy sinks to a leaf, where the last array element is
placed and sifted up if needed — typically zero levels since the
last array element tends to be large.
In the common case, work per extract drops from 2d comparisons
+ 3d copies to d comparisons + d copies: roughly half the
comparisons and a third of the data movement. The sift-up phase
can add work when the last element is smaller than ancestors of
the leaf vacancy, but this is rare in practice.
Simplify prio_queue_replace() to a plain get+put sequence. This
is semantically equivalent: the old implementation wrote to slot 0
and sifted down, which has the same observable effect as removing
the root and inserting a new element. No caller observes queue
state between the two operations. The previous implementation
shared sift_down_root() with get, but the cascade approach no
longer accommodates that cleanly since sift_down_root() now
expects the element to reinsert at queue->array[queue->nr], left
there by prio_queue_get() after decrementing nr. This is fine in
practice: replace is only called from pop_most_recent_commit()
(fetch-pack, object-name, walker) and show-branch — none of
which appear in any hot path.
A synthetic benchmark (10 rounds of 10M put+get cycles, ascending
integer keys, CPU-pinned, median of 3 runs, same compiler and
Makefile flags) shows consistent improvement across all queue
sizes, with no regressions:
queue width baseline cascade speedup
------------------------------------------------
10 4.32s 3.97s 1.09x
100 7.95s 6.49s 1.23x
1,000 11.30s 9.66s 1.17x
10,000 16.34s 14.15s 1.16x
100,000 21.43s 18.66s 1.15x
With descending keys (worst case — the last element always sinks
to a leaf in both approaches) the cascade still wins slightly
(1-4%) by replacing swaps with copies, and never regresses.
In end-to-end git commands the improvement is modest because
sift_down_root is only ~8% of total runtime. Profiling
rev-list --count on a 2.5M-commit monorepo shows sift_down_root
dropping from 8.2% to 0.4% of total runtime. The improvement
scales with DAG width: wider DAGs produce larger priority queues,
amplifying the per-level savings. In small or narrow repos the
queues stay shallow and the effect is negligible.
Signed-off-by: Kristofer Karlsson <krka@spotify.com>
---
prio-queue: use cascade-down sift for faster extract-min
Hi, I am not sure this is just noise or not but I thought it at least
was interesting.
I looked into the internals of prio_queue and found it was technically
doing too much work and could be simplified/optimized. I found I could
optimize it by ~20% for the common case (adding commits that would
typically end up far back in the queue) but only ~1% for the reverse
case (adding things to the front of the prio queue). The average speedup
is somewhere in between I suppose. That said, this is not really the
bottleneck so the overall boost seems to be around ~3-4% improvement for
repos with wide DAGs.
I would normally classify this as not urgent or important, but I think
the advantage is that the change is very small and simple and it already
has good unit tests (t/unit-tests/u-prio-queue.c).
With that said, here are the details:
The prio_queue_get impl is based on removing the root entry, then moving
the very last element into the root slot, then sifting it down into the
right place. This uses both comparisons between sibling elements in the
heap as well as comparisons between the element to add and one of the
siblings. Then it uses swap operations to move things correctly.
This patch instead promotes the smaller child upward at each level,
leaving a vacancy that sinks to a leaf, then places the removed element
there with a short sift-up to keep the heap balanced.
We can analytically compare this - for a sift-distance of d we can
reason about the number of operations to execute.
Before: 2d comparisons + 3d copies
After: d comparisons + d copies
After changing sift_down in this way, the replace operation can't simply
depend on it anymore, so I reimplemented it as a sequence of get + put.
This is technically correct but maybe not as efficient. However, I am
not sure that it matters, since I couldn't see any usage of the replace
operation in any hot path.
Performance: Profiling git rev-list --count on a 2.5M-commit monorepo
shows sift_down_root dropping from 8.2% to 0.4% of total runtime,
effectively eliminated as significant overhead.
Synthetic benchmark 10 rounds of 10M put+get cycles, CPU-pinned, median
of 3 runs, same compiler and Makefile flags.
Ascending keys (git's typical pattern -- parents have lower priority
than children):
queue width baseline patched speedup
10 4.32s 3.97s 1.09x
100 7.95s 6.49s 1.23x
1,000 11.30s 9.66s 1.17x
10,000 16.34s 14.15s 1.16x
100,000 21.43s 18.66s 1.15x
Descending keys (worst case — last element always sinks to leaf in both
approaches):
queue width baseline patched speedup
10 4.84s 4.78s 1.01x
100 9.43s 9.20s 1.03x
1,000 15.28s 14.71s 1.04x
10,000 23.61s 23.49s 1.01x
100,000 29.16s 28.22s 1.03x
No regressions in any scenario.
End-to-end benchmarks
All benchmarks use a benchmark setup of 1 warmup run followed by 10
timed runs. Each configuration is built from the same source tree and
tested on the same repo in alternating order.
linux kernel (1.4M commits) — range v5.0..v6.0 (311K commits):
Command baseline patched speedup
rev-list --count v5.0..v6.0 455ms 440ms 1.04x
I also ran it on git.git but did not see any performance diff at all,
due to the size and narrow DAG.
The improvement scales with DAG width: wider DAGs produce larger
priority queues, amplifying the per-level savings. In small or narrow
repositories the priority queues stay shallow and the sift-down cost is
already negligible, so the change is not noticeable.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2132%2Fspkrka%2Fcascade-sift-down-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2132/spkrka/cascade-sift-down-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2132
prio-queue.c | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/prio-queue.c b/prio-queue.c
index 9748528ce6..18005c43c4 100644
--- a/prio-queue.c
+++ b/prio-queue.c
@@ -62,17 +62,21 @@ static void sift_down_root(struct prio_queue *queue)
{
size_t ix, child;
- /* Push down the one at the root */
- for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
- child = ix * 2 + 1; /* left */
+ for (ix = 0; (child = ix * 2 + 1) < queue->nr; ix = child) {
if (child + 1 < queue->nr &&
compare(queue, child, child + 1) >= 0)
child++; /* use right child */
+ queue->array[ix] = queue->array[child];
+ }
- if (compare(queue, ix, child) <= 0)
+ /* Place queue->array[queue->nr] (left by caller) and sift up. */
+ queue->array[ix] = queue->array[queue->nr];
+ while (ix) {
+ size_t parent = (ix - 1) / 2;
+ if (compare(queue, parent, ix) <= 0)
break;
-
- swap(queue, child, ix);
+ swap(queue, parent, ix);
+ ix = parent;
}
}
@@ -89,7 +93,6 @@ void *prio_queue_get(struct prio_queue *queue)
if (!--queue->nr)
return result;
- queue->array[0] = queue->array[queue->nr];
sift_down_root(queue);
return result;
}
@@ -111,8 +114,7 @@ void prio_queue_replace(struct prio_queue *queue, void *thing)
queue->array[queue->nr - 1].ctr = queue->insertion_ctr++;
queue->array[queue->nr - 1].data = thing;
} else {
- queue->array[0].ctr = queue->insertion_ctr++;
- queue->array[0].data = thing;
- sift_down_root(queue);
+ prio_queue_get(queue);
+ prio_queue_put(queue, thing);
}
}
base-commit: c69baaf57ba26cf117c2b6793802877f19738b0d
--
gitgitgadget
^ permalink raw reply related
* Draft of Git Rev News edition 135
From: Christian Couder @ 2026-05-31 11:01 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Jakub Narebski, Markus Jansen, Kaartic Sivaraam,
Štěpán Němec, Taylor Blau,
Johannes Schindelin, Derrick Stolee, Elijah Newren, Jeff King,
Matthias A.
Hi everyone,
A draft of a new Git Rev News edition is available here:
https://github.com/git/git.github.io/blob/master/rev_news/drafts/edition-135.md
Everyone is welcome to contribute in any section either by editing the
above page on GitHub and sending a pull request, or by commenting on
this GitHub issue:
https://github.com/git/git.github.io/issues/844
You can also reply to this email.
In general all kinds of contributions, for example proofreading,
suggestions for articles or links, help on the issues in GitHub,
volunteering for being interviewed and so on, are very much
appreciated.
I tried to Cc everyone who appears in this edition, but maybe I missed
some people, sorry about that.
Jakub, Markus, Kaartic and I plan to publish this edition on Tuesday
June 2nd, 2026.
Thanks,
Christian.
^ permalink raw reply
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