Compare commits

...

676 commits

Author SHA1 Message Date
Stefan Haller d0ede21e9c
Allow running tests from a tarball (#6003)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
Running integration tests from a tarball was never possible, but running
unit tests (`go test ./... -short`) was; this broke with v0.64.1
(specifically, with 34da956f5d). Make that possible again, and for all
tests now, including integration tests.
2026-09-09 09:30:10 +02:00
Stefan Haller 8b049be31d Find the lazygit root directory by go.mod instead of .git
Running the tests in an exported source tarball fails with "must run in
lazy project folder or child folder". GetLazyRootDirectory searches the
working directory and its parents for a .git directory, and a tarball
doesn't have one. This has always affected the integration tests; since
34da956f5d a unit test calls the function too, so now even
`go test ./... -short` fails.

Search for the go.mod file that declares lazygit's module instead. It
ships in tarballs, and there is exactly one of it per source tree.

Put the function in our own pkg/utils rather than change lazycore's; the
criterion is specific to lazygit, and I don't feel like making a change
to lazycore.

Return an error rather than call log.Fatal, and report it from the two
callers that run under `go test`. In a test binary, log.Fatal exits
without attributing the failure to any test. That is the failure mode
34da956f5d set out to remove. The remaining callers are development
tools that have nothing useful to do without the root directory; they
keep exiting, now through MustFindLazygitRootDirectory.

Also stop the search at the root of the file system rather than at "/".
On Windows the old loop walks up to "C:\" and then spins there forever.
2026-09-09 09:27:44 +02:00
Stefan Haller a8dc4aaf1b AGENTS.md addition 2026-09-07 09:51:49 +02:00
Stefan Haller c07f4d381b
Update docs and schema for release (#5995)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
2026-09-05 16:58:42 +02:00
Stefan Haller f9dd66dbf0 Update docs and schema for release 2026-09-05 16:55:40 +02:00
Stefan Haller 21b649f12e
Update translations from Crowdin (#5994) 2026-09-05 16:49:00 +02:00
Stefan Haller 3ba69d4965 Update translations from Crowdin 2026-09-05 16:46:18 +02:00
Stefan Haller abe273dd3e
Bump github.com/sirupsen/logrus from 1.9.4 to 1.10.2 (#5987)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus)
from 1.9.4 to 1.10.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sirupsen/logrus/releases">github.com/sirupsen/logrus's
releases</a>.</em></p>
<blockquote>
<h2>v1.10.2</h2>
<h1>Logrus v1.10.2</h1>
<p>This is a small maintenance release that updates
<code>github.com/stretchr/testify</code> to v1.12.1, removing the legacy
<code>gopkg.in/yaml.v3</code> dependency from Logrus' dependency graph.
There are no functional changes in this release.</p>
<p>Dependency Changes</p>
<ul>
<li>update github.com/stretchr/testify to v1.12.1</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sirupsen/logrus/compare/v1.10.1...v1.10.2">https://github.com/sirupsen/logrus/compare/v1.10.1...v1.10.2</a></p>
<h2>v1.10.1</h2>
<h1>Logrus v1.10.1</h1>
<p>This patch release fixes two issues in field formatting and
handling:</p>
<ul>
<li>Fix a regression introduced in v1.10.0 where
<code>TextFormatter</code> could panic
when formatting nil or panicking <code>error</code> and
<code>fmt.Stringer</code> values.</li>
<li>Allow function-backed values implementing <code>error</code> to be
used with
<code>WithError</code>, <code>WithField</code>, and
<code>WithFields</code>.</li>
</ul>
<p>Dependency Changes</p>
<ul>
<li>update github.com/stretchr/testify to v1.12.0</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sirupsen/logrus/compare/v1.10.0...v1.10.1">https://github.com/sirupsen/logrus/compare/v1.10.0...v1.10.1</a></p>
<h2>v1.10.0</h2>
<h1>Logrus v1.10.0</h1>
<p>This release focuses on substantial performance improvements,
concurrency correctness, and better interoperability with modern Go
logging APIs.</p>
<h2>🚀 Performance</h2>
<p>Major improvements across <code>TextFormatter</code>, entry handling,
and common logger paths:</p>
<ul>
<li>~17% lower geomean runtime across the benchmark suite</li>
<li>~27% higher geomean formatter throughput</li>
<li>Common enabled logging paths are ~30–44% faster</li>
<li><code>WithError</code> is ~40% faster</li>
<li>Chained fields are ~46% faster</li>
<li><code>TextFormatter</code> paths are up to ~40% faster</li>
<li>Allocation counts are reduced by ~25–74% across measured
<code>TextFormatter</code> cases, with the largest reductions in colored
output</li>
</ul>
<p>The improvements also show up in complete logger paths:</p>
<ul>
<li>Logger + <code>TextFormatter</code> is ~31% faster, with ~24% fewer
allocations</li>
<li>Logger + <code>JSONFormatter</code> is ~21% faster, with ~10% fewer
allocations</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md">github.com/sirupsen/logrus's
changelog</a>.</em></p>
<blockquote>
<h2>1.10.2</h2>
<p>Changed:</p>
<ul>
<li>Update <code>github.com/stretchr/testify</code> to v1.12.1, removing
the legacy
<code>gopkg.in/yaml.v3</code> dependency.</li>
</ul>
<h2>1.10.1</h2>
<p>Fixes:</p>
<ul>
<li>Fix a regression introduced in v1.10.0 where
<code>TextFormatter</code> could panic
when formatting nil or panicking <code>error</code> and
<code>fmt.Stringer</code> values.</li>
<li>Allow function-backed implementations of <code>error</code> as field
values.</li>
</ul>
<h2>1.10.0</h2>
<p>Fixes:</p>
<ul>
<li>Fix reentrant logging deadlocks in formatter paths.</li>
<li>Fix race conditions in formatter and entry handling.</li>
<li>Fix generic <code>Log</code>, <code>Logf</code>, <code>Logln</code>,
and <code>LogFn</code> methods unexpectedly
panicking when called with <code>PanicLevel</code>. Use the
corresponding <code>Panic</code>
methods when panic behavior is desired.</li>
<li>Improve concurrency safety around formatter and hook access.</li>
</ul>
<p>Features:</p>
<ul>
<li>Add <code>slog</code> hook for forwarding Logrus entries to
<code>log/slog</code>.</li>
<li>Add <code>slog.Handler</code> for forwarding <code>log/slog</code>
records to a Logrus logger,
including levels, fields, groups, context, time, and optional caller
reporting. The hook and handler can also be combined to help migrate
between Logrus and <code>log/slog</code>.</li>
<li>Add minimal, composable logging interfaces for each log level. This
enables
consumers to depend on narrower interfaces, making it easier to
substitute
or adapt logging implementations.</li>
<li>Allow <code>Entry.Caller</code> to be set explicitly and preserve it
across derived
entries, enabling custom caller detection without Logrus overwriting
caller information when <code>ReportCaller</code> is enabled.</li>
</ul>
<p>Changed:</p>
<ul>
<li>Raise minimum supported Go version to 1.23.</li>
<li>TextFormatter now renders <code>[]byte</code> values as raw/quoted
strings instead of slice-of-ints.</li>
<li>TextFormatter now uses distinct dimmed colors for debug and trace
output.</li>
<li>TextFormatter now automatically enables colors on Windows terminals
with ANSI support,
matching the behavior on other platforms.</li>
<li><code>Entry.HasCaller</code> is now deprecated in favor of checking
<code>Entry.Caller</code> directly.</li>
<li>Deprecated <code>MutexWrap</code>, which was unintentionally exposed
as public API.
It remains available as an alias for compatibility but should not be
used</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="6d6a132bc0"><code>6d6a132</code></a>
Merge pull request <a
href="https://redirect.github.com/sirupsen/logrus/issues/1586">#1586</a>
from thaJeztah/prepare_v1.10.2</li>
<li><a
href="4f9465318c"><code>4f94653</code></a>
update changelog for v1.10.2</li>
<li><a
href="87434bb3a7"><code>87434bb</code></a>
Merge pull request <a
href="https://redirect.github.com/sirupsen/logrus/issues/1585">#1585</a>
from thaJeztah/bump_testify</li>
<li><a
href="e7d2120300"><code>e7d2120</code></a>
chore(deps): bump github.com/stretchr/testify v1.12.1</li>
<li><a
href="8b673a9eb3"><code>8b673a9</code></a>
Merge pull request <a
href="https://redirect.github.com/sirupsen/logrus/issues/1583">#1583</a>
from thaJeztah/release_1.10.1</li>
<li><a
href="0b920add8d"><code>0b920ad</code></a>
Merge pull request <a
href="https://redirect.github.com/sirupsen/logrus/issues/1584">#1584</a>
from thaJeztah/more_coverage</li>
<li><a
href="5e20694a7a"><code>5e20694</code></a>
TextFormatter: cover nil pointer method receivers</li>
<li><a
href="83127326e4"><code>8312732</code></a>
update changelog for v1.10.1</li>
<li><a
href="e987a4036b"><code>e987a40</code></a>
Merge pull request <a
href="https://redirect.github.com/sirupsen/logrus/issues/1582">#1582</a>
from thaJeztah/panic_handler</li>
<li><a
href="17d574b11e"><code>17d574b</code></a>
TextFormatter: recover panics from Error and String methods</li>
<li>Additional commits viewable in <a
href="https://github.com/sirupsen/logrus/compare/v1.9.4...v1.10.2">compare
view</a></li>
</ul>
</details>
<br />
2026-09-05 16:38:57 +02:00
dependabot[bot] 9182ac9ebc
Bump github.com/sirupsen/logrus from 1.9.4 to 1.10.2
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.4 to 1.10.2.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.9.4...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 14:21:01 +00:00
Stefan Haller 9aa2c0b85a
Bump github.com/gdamore/tcell/v3 from 3.4.1 to 3.4.2 (#5970)
Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell)
from 3.4.1 to 3.4.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/gdamore/tcell/releases">github.com/gdamore/tcell/v3's
releases</a>.</em></p>
<blockquote>
<h2>Version 3.4.2 Bug Fix Release</h2>
<h2>What's Changed</h2>
<ul>
<li>chore(deps): bump actions/setup-go from 6 to 7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/gdamore/tcell/pull/1141">gdamore/tcell#1141</a></li>
<li>perf: skip grapheme iterator for printable ASCII by <a
href="https://github.com/ayn2op"><code>@​ayn2op</code></a> in <a
href="https://redirect.github.com/gdamore/tcell/pull/1145">gdamore/tcell#1145</a></li>
<li>chore(deps): bump github.com/lucasb-eyer/go-colorful from 1.4.0 to
1.4.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/gdamore/tcell/pull/1153">gdamore/tcell#1153</a></li>
<li>chore(deps): bump golang.org/x/text from 0.40.0 to 0.41.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/gdamore/tcell/pull/1157">gdamore/tcell#1157</a></li>
<li>fix(tscreen): guard inputLoop keyQ send against shutdown deadlock
(revives <a
href="https://redirect.github.com/gdamore/tcell/issues/673">#673</a>) by
<a href="https://github.com/gmlewis"><code>@​gmlewis</code></a> in <a
href="https://redirect.github.com/gdamore/tcell/pull/1155">gdamore/tcell#1155</a></li>
<li>fix(st): The st terminal is very limited - disable extensions that
br… by <a href="https://github.com/gdamore"><code>@​gdamore</code></a>
in <a
href="https://redirect.github.com/gdamore/tcell/pull/1160">gdamore/tcell#1160</a></li>
<li>fix: Fix SS3 application-keypad sequences (fixes <a
href="https://redirect.github.com/gdamore/tcell/issues/1159">#1159</a>)
by <a href="https://github.com/gdamore"><code>@​gdamore</code></a> in <a
href="https://redirect.github.com/gdamore/tcell/pull/1161">gdamore/tcell#1161</a></li>
<li>fix(input): extend escape sequence timeouts by <a
href="https://github.com/gdamore"><code>@​gdamore</code></a> in <a
href="https://redirect.github.com/gdamore/tcell/pull/1162">gdamore/tcell#1162</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gmlewis"><code>@​gmlewis</code></a> made
their first contribution in <a
href="https://redirect.github.com/gdamore/tcell/pull/1155">gdamore/tcell#1155</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/gdamore/tcell/compare/v3.4.1...v3.4.2">https://github.com/gdamore/tcell/compare/v3.4.1...v3.4.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c72e284716"><code>c72e284</code></a>
fix(input): extend escape sequence timeouts (<a
href="https://redirect.github.com/gdamore/tcell/issues/1162">#1162</a>)</li>
<li><a
href="36892cde4e"><code>36892cd</code></a>
test: cover modified SS3 keypad runes</li>
<li><a
href="e794ff2439"><code>e794ff2</code></a>
fix: Fix SS3 application-keypad sequences (fixes <a
href="https://redirect.github.com/gdamore/tcell/issues/1159">#1159</a>)</li>
<li><a
href="331861bcbd"><code>331861b</code></a>
fix(st): The st terminal is very limited - disable extensions that break
it (...</li>
<li><a
href="22ae0b117b"><code>22ae0b1</code></a>
fix(tscreen): guard inputLoop keyQ send against shutdown deadlock</li>
<li><a
href="ec458f1327"><code>ec458f1</code></a>
chore(deps): bump golang.org/x/text from 0.40.0 to 0.41.0</li>
<li><a
href="8d07aa89c2"><code>8d07aa8</code></a>
chore(deps): bump github.com/lucasb-eyer/go-colorful from 1.4.0 to
1.4.1</li>
<li><a
href="3182f3e975"><code>3182f3e</code></a>
perf: skip grapheme iterator for printable ASCII</li>
<li><a
href="45d70ee4ab"><code>45d70ee</code></a>
chore(deps): bump actions/setup-go from 6 to 7</li>
<li>See full diff in <a
href="https://github.com/gdamore/tcell/compare/v3.4.1...v3.4.2">compare
view</a></li>
</ul>
</details>
<br />
2026-09-05 16:19:12 +02:00
dependabot[bot] 9d128e8503
Bump github.com/gdamore/tcell/v3 from 3.4.1 to 3.4.2
Bumps [github.com/gdamore/tcell/v3](https://github.com/gdamore/tcell) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv3.md)
- [Commits](https://github.com/gdamore/tcell/compare/v3.4.1...v3.4.2)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v3
  dependency-version: 3.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 14:06:21 +00:00
Stefan Haller 3c95d635cb
Bump github.com/stretchr/testify from 1.11.1 to 1.12.1 (#5968)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify)
from 1.11.1 to 1.12.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/stretchr/testify/releases">github.com/stretchr/testify's
releases</a>.</em></p>
<blockquote>
<h2>v1.12.1</h2>
<p>This is the first release which has the minimum dependencies
practical in testify v1. The last remaining dependencies are
github.com/stretchr/objx which itself has no dependencies, and
go.yaml.in/yaml/v3. Removing objx would require v2, it cannot be
vendored. Removing YAML would require vendoring the yaml library, which
would do more harm than good. It's better to become aware of
vulnerabilities in the official yaml package than to attempt to maintain
our own.</p>
<h2>What's Changed</h2>
<ul>
<li>Change yaml library to <code>go.yaml.in/yaml/v3</code> by <a
href="https://github.com/harryzcy"><code>@​harryzcy</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1935">stretchr/testify#1935</a></li>
<li>change yaml library to go.yaml.in/yaml/v3 by <a
href="https://github.com/boekkooi-impossiblecloud"><code>@​boekkooi-impossiblecloud</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1772">stretchr/testify#1772</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/harryzcy"><code>@​harryzcy</code></a>
made their first contribution in <a
href="https://redirect.github.com/stretchr/testify/pull/1935">stretchr/testify#1935</a></li>
<li><a
href="https://github.com/boekkooi-impossiblecloud"><code>@​boekkooi-impossiblecloud</code></a>
made their first contribution in <a
href="https://redirect.github.com/stretchr/testify/pull/1772">stretchr/testify#1772</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1">https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1</a></p>
<h2>What's Changed</h2>
<ul>
<li>Change yaml library to <code>go.yaml.in/yaml/v3</code> by <a
href="https://github.com/harryzcy"><code>@​harryzcy</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1935">stretchr/testify#1935</a></li>
<li>change yaml library to go.yaml.in/yaml/v3 by <a
href="https://github.com/boekkooi-impossiblecloud"><code>@​boekkooi-impossiblecloud</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1772">stretchr/testify#1772</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/harryzcy"><code>@​harryzcy</code></a>
made their first contribution in <a
href="https://redirect.github.com/stretchr/testify/pull/1935">stretchr/testify#1935</a></li>
<li><a
href="https://github.com/boekkooi-impossiblecloud"><code>@​boekkooi-impossiblecloud</code></a>
made their first contribution in <a
href="https://redirect.github.com/stretchr/testify/pull/1772">stretchr/testify#1772</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1">https://github.com/stretchr/testify/compare/v1.12.0...v1.12.1</a></p>
<h2>v1.12.0</h2>
<h2>What's Changed</h2>
<h3>Functional Changes</h3>
<ul>
<li>assert: make *AssertionFunc types just aliases by <a
href="https://github.com/dolmen"><code>@​dolmen</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1563">stretchr/testify#1563</a></li>
</ul>
<h3>Fixes</h3>
<ul>
<li>mock: avoid panic when expected type is nil in Arguments.Diff by <a
href="https://github.com/mutaiib"><code>@​mutaiib</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1775">stretchr/testify#1775</a></li>
<li>mock: revert to pre-v1.11.0 argument matching behavior for mutating
stringers by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1786">stretchr/testify#1786</a></li>
<li>suite: validate method signatures and continue execution for valid
tests by <a
href="https://github.com/vyas-git"><code>@​vyas-git</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1665">stretchr/testify#1665</a></li>
<li>assert.PanicsWithError: report error message by <a
href="https://github.com/olivergondza"><code>@​olivergondza</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1400">stretchr/testify#1400</a></li>
<li>assert: IsIncreasing et al can return false w/out failing by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1787">stretchr/testify#1787</a></li>
<li>add type to error message of assert.Same by <a
href="https://github.com/egawata"><code>@​egawata</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1792">stretchr/testify#1792</a></li>
<li>mock.AssertExpectationsForObjects fix panic with wrong testObject
type. by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1795">stretchr/testify#1795</a></li>
<li>assert: truncate very long objects in test failure messages by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1646">stretchr/testify#1646</a></li>
<li>assert: fix NotSubset error messages using %#v instead of %q (fixes
<a
href="https://redirect.github.com/stretchr/testify/issues/1800">#1800</a>)
by <a href="https://github.com/nghiack7"><code>@​nghiack7</code></a> in
<a
href="https://redirect.github.com/stretchr/testify/pull/1888">stretchr/testify#1888</a></li>
<li>suite: prevent panic when SetupTest skips with HandleStats by <a
href="https://github.com/blackwell-systems"><code>@​blackwell-systems</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1877">stretchr/testify#1877</a></li>
</ul>
<h3>Documentation, Build &amp; CI</h3>
<ul>
<li>CI: test also with Go 1.23 by <a
href="https://github.com/dolmen"><code>@​dolmen</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1783">stretchr/testify#1783</a></li>
<li>Vendor unmaintained github.com/pmezard/go-difflib by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1708">stretchr/testify#1708</a></li>
<li>Promote ccoVeille to maintainer by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1784">stretchr/testify#1784</a></li>
<li>build(deps): bump actions/setup-go from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/stretchr/testify/pull/1790">stretchr/testify#1790</a></li>
<li>assert.YAMLEq: Document mutlidoc behavior by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1791">stretchr/testify#1791</a></li>
<li>_codegen: copy dependency github.com/ernesto-jimenez/gogen/imports
by <a href="https://github.com/dolmen"><code>@​dolmen</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1782">stretchr/testify#1782</a></li>
<li>doc: remove ineffective inline code blocks by <a
href="https://github.com/brackendawson"><code>@​brackendawson</code></a>
in <a
href="https://redirect.github.com/stretchr/testify/pull/1714">stretchr/testify#1714</a></li>
<li>Tag generated assertions as non-generated in new .gitattributes by
<a href="https://github.com/ubunatic"><code>@​ubunatic</code></a> in <a
href="https://redirect.github.com/stretchr/testify/pull/1815">stretchr/testify#1815</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="959dbdacf1"><code>959dbda</code></a>
Merge pull request <a
href="https://redirect.github.com/stretchr/testify/issues/1935">#1935</a>
from harryzcy/yaml-update</li>
<li><a
href="9bb71766fa"><code>9bb7176</code></a>
Update go.yaml.in/yaml/v3 to v3.0.5</li>
<li><a
href="001eb7946b"><code>001eb79</code></a>
Merge pull request <a
href="https://redirect.github.com/stretchr/testify/issues/1905">#1905</a>
from Kentzo/patch-1</li>
<li><a
href="ad40f384b1"><code>ad40f38</code></a>
Merge pull request <a
href="https://redirect.github.com/stretchr/testify/issues/1906">#1906</a>
from stretchr/dependabot/github_actions/actions/chec...</li>
<li><a
href="3bae01746b"><code>3bae017</code></a>
build(deps): bump actions/checkout from 6.0.2 to 6.0.3</li>
<li><a
href="f8c01f33a3"><code>f8c01f3</code></a>
mock: Mock.Return does not exist anymore</li>
<li><a
href="12f8b5612e"><code>12f8b56</code></a>
Merge pull request <a
href="https://redirect.github.com/stretchr/testify/issues/1563">#1563</a>
from stretchr/make-AssertionFunc-types-aliases</li>
<li><a
href="a11649e427"><code>a11649e</code></a>
assert: make *AssertionFunc type just aliases</li>
<li><a
href="dc20f41986"><code>dc20f41</code></a>
Merge pull request <a
href="https://redirect.github.com/stretchr/testify/issues/1890">#1890</a>
from stretchr/dolmen/codegen-modernize</li>
<li><a
href="098f8d75b3"><code>098f8d7</code></a>
_codegen: use strings.Builder</li>
<li>Additional commits viewable in <a
href="https://github.com/stretchr/testify/compare/v1.11.1...v1.12.1">compare
view</a></li>
</ul>
</details>
<br />
2026-09-05 16:04:41 +02:00
dependabot[bot] 910d1e49cf
Bump github.com/stretchr/testify from 1.11.1 to 1.12.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.1 to 1.12.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.11.1...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 14:00:38 +00:00
Stefan Haller 91970a1202
Bump github.com/xo/terminfo from 0.0.0-20220910002029-abceb7e1c41e to 1.0.0 (#5950)
Bumps [github.com/xo/terminfo](https://github.com/xo/terminfo) from
0.0.0-20220910002029-abceb7e1c41e to 1.0.0.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/xo/terminfo/commits/v1.0.0">compare
view</a></li>
</ul>
</details>
<br />
2026-09-05 15:58:42 +02:00
dependabot[bot] 730b590ce6
Bump github.com/xo/terminfo
Bumps [github.com/xo/terminfo](https://github.com/xo/terminfo) from 0.0.0-20220910002029-abceb7e1c41e to 1.0.0.
- [Commits](https://github.com/xo/terminfo/commits/v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/xo/terminfo
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 13:55:33 +00:00
Stefan Haller 9673a627ca
Re-render the focused main view if its content changes while it is being searched (#5993)
When searching the focused main view using `/`, any updates to its
content were ignored because back when we introduced the focused main
view feature we couldn't make it work; search mode couldn't cope well
with the view content changing under it.

In this PR we make that work, and remove the limitation. Along the way
we fix a bunch of other related problems; some are only theoretical race
conditions that have been found by reading the code, but never observed
in reality; some are real problems that are too edge-casey to describe
in detail. See the individual commit messages for details.
2026-09-05 15:50:28 +02:00
Stefan Haller 114d3a5a25 Render the focused main view again while it is being searched
A refresh left the focused main view alone while a search was on, so the
diff on screen stayed as it was however much the working tree had moved
on underneath it. The search could not cope with the content changing
under it, and leaving the content alone was the way around that.

It can cope now. The positions are worked out again from whatever the
view holds, and the status with them, so render it like any other.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller 811b3fbdd1 Show the search status of what a re-rendered view now holds
Rendering a view's content again while a search is on leaves the "x of y"
describing the content that has just been replaced. The status is worked
out when the search is typed and again when a key steps through the
matches, and a render is neither. Change the diff context size while
searching the focused main view, and the count stays as it was, however
many matches the wider context brought in or took away.

Run the search again over the new content once the render has finished
putting it there.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller 04a7ae3ec8 Read a view that is being searched to the end when it renders again
Opening the search prompt reads the whole of the view's content, so that
the search counts every match in it. Rendering the content again reads
only as much as the scrollbar needs, so the matches below that point are
lost. The "x of y" drops to what the shortened content holds, and grows
again as the user scrolls far enough to load more.

Read to the end while a search is on, the way opening the prompt does.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller 0505e778b3 Work the search positions out when they are read, not on each line written
A view's search positions were worked out again from every write, and
each of those walks the whole view. Content arrives a line at a time, so
rendering into a searched view costs a walk per line. Streaming 2000
lines takes 565ms, where the same render into an unsearched view takes
about 10ms.

Mark the positions stale on a write instead, and work them out where they
are read: when the view is drawn, when a key steps through the matches,
when the status is asked for. That is at most once a frame, and the same
2000 lines now take 8ms.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller 4c90bc334c Bring the current search match back into range when the matches change
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller b54318e80c Add a test for the current search match after the matches change
Search a view, step to the last match, then have the view re-rendered
with fewer matches in it, and the status reads "3 of 1". The positions
are worked out again whenever the content changes, but the index into
them stays where it was. Stepping on from there indexes the positions
out of range and panics.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller acb6e48a98 Hold a task while a view is read to its end
ReadToEnd reads the rest of a view's content on the render task's own
goroutine, and calls back once it has. Nothing held a task for that, so
lazygit counted as idle from the moment the caller returned until the
callback ran. The search prompt in the focused main view opens from such
a callback, so an integration test takes the idle report as its cue to
carry on, and presses its next key while the prompt is not open yet.

Hold the task in ReadToEnd rather than in the caller, so that every
caller is covered (see docs/dev/Busy.md).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller dd2a1a634f Answer every read request, whether or not a task is still serving
A caller of ReadLines or ReadToEnd is told that the content it asked for
has been read by the request's Then being called. A task that reaches the
end of its input answers the requests still queued behind the one it was
serving, but a task that is stopped drops them, and their callers wait
for a callback that never comes. Pressing "/" in the focused main view
opens the search prompt from such a callback, so if a re-render replaces
the task at that moment the prompt never opens.

Answering them as the read loop ends would leave a request handed over
after that point unanswered, and there is a window for one. A caller
reads the channel to send on, and can reach the send itself only once the
loop has gone. So hand requests over through a queue instead. Asking
whether a task is there and giving it the request are one step, as are
taking the task away and handing back what it never answered; a request
made in between goes back to the caller to answer.

The queue is unbounded rather than a fixed-size channel, for the reasons
gocui's userEventQueue is. Requests are handed over from the UI thread,
where a blocking send would deadlock against the task waiting to be let
go, and a fixed channel that fills up leaves only blocking, dropping,
reordering or panicking to choose between.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller e148959865 Add a test for read requests waiting when a task is stopped
Ask a view buffer manager to read to the end of its content twice over,
then stop the task before it has served either request, as a re-render
replacing it does. Only the request it had already picked up is answered;
the one still queued behind it is dropped, and its caller waits for a
callback that never comes.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 15:09:52 +02:00
Stefan Haller ef3a4c71cc Addition to AGENTS.md 2026-09-05 15:09:52 +02:00
Stefan Haller e0b2a5081d
Fix some obscure selection highlighting bugs (#5990)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
2026-09-03 22:57:21 +02:00
Stefan Haller d2dc38ee87 Drop the highlight fixups the context stack now makes unnecessary
Two places nudged the flags because nothing else would: switching repos,
where the view focused in the repo being left is not the one focused in the
repo being entered, and tabbing from the suggestions list back to the
prompt, which replaces the top of the stack rather than popping it, so the
suggestions context never hears that it lost the focus. Both are just a
context leaving the stack now.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller ec3f681ebf Derive the selection highlight from the context stack
A view drew a selection because something told it to, from four places on
three different schedules: a context being focused, a context losing focus,
a context being activated over another one, and a list being re-rendered.
Whether the flags ended up describing the state of the app depended on
which of those had run last, and the last one to run was often none of
them: a refresh only re-focuses the view that has the focus, so a list
whose contents changed underneath an unfocused panel kept whichever
highlight it happened to have.

Derive both flags instead, in one place, from the two things they mean: a
view shows a selection while its context is on the stack and has something
to select, and the context the user is in shows an active one where the
ones behind it show inactive ones. Nothing else needs to say anything about
highlighting, so nothing else can leave a view saying something untrue
about where the focus is.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller d1707d5dd2 Re-render the main view without re-focusing the panel beneath it
Toggling whitespace needs the panel beneath to render its diff again, which
is what HandleRenderToMain is for; HandleFocus does that and also everything
else that belongs to a panel gaining the focus, which this panel already has
or, when the focus is in the main view, does not want. Re-selecting its
current item is harmless, but re-deriving its highlight as a focused panel's
is not: the selection turns bright while the user is somewhere else.

Changing the context size and switching diff renderers already ask for a
re-render this way.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller e8009599e0 Demonstrate that toggling whitespace undims the panel beneath the main view
Toggling whitespace re-focuses the side panel to re-render the diff, which
also re-derives that panel's highlight — as though the panel had the focus,
which it doesn't.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller dd01d67879 Demonstrate that an unfocused list's selection goes stale
A refresh only re-derives the highlight of the view that has the focus, so
a list whose contents change while the user is somewhere else keeps the
selection it had: none for a list that just got its first item, and one
over nothing for a list that just lost its last.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller 4b391acec0 Ask each context whether it has content to select
Whether a view draws a selection is about to be derived in one place from
the context stack, which needs to ask any context — list or not — whether
there is something for a selection to sit on. Name the existing flag after
that question, and let a list context answer it from its length.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller eb760ee928 Cover how a view's selection follows the focus in and out of a context
Nothing said that a context leaving the stack takes its selection with it,
which the work coming up is about to make the rule for every view. Two
places already depend on it and are held together by hand: switching repos,
where the view focused in the repo being left is not the one focused in the
repo being entered, and tabbing from the suggestions list back to the
prompt, which replaces the top of the stack rather than popping it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller 1b2bd85fc5 Let a test assert how a view draws its selection
The selected line of a view says nothing about whether a selection is drawn
over it, or which of the two ways it is drawn in, and those are what the
tests coming up are about.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:19:04 +02:00
Stefan Haller e7de68331a
Validate the context names in the "context" field of custom commands (#5989)
If a custom command's "context" field contains a context name that
doesn't exist, lazygit panics when building the keybindings. This could
happen either because of a typo, or because a context is removed or
renamed in a later version. Prevent the panic by validating those names
at config load time, and rejecting the config as invalid there, like we
do for other config errors.
2026-09-03 21:18:48 +02:00
Stefan Haller e0927d4faf Validate the context names in the "context" field of custom commands
If a custom command's "context" field contains a context name that
doesn't exist, lazygit panics when building the keybindings. This could
happen either because of a typo, or because a context is removed or
renamed in a later version. Prevent the panic by validating those names
at config load time, and rejecting the config as invalid there, like we
do for other config errors.

The gui package owns the list, but can't be imported from here, so it is
mirrored and a test over there ensures the copies stay in sync.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 21:07:32 +02:00
Stefan Haller 3f6be3b3ee
Allow filtering the keybindings and recent repos menus more directly (simply by typing) (#5985)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
All menus in lazygit can be filtered by pressing the `/` key; for most
menus which only show a handful of choices this is not really needed,
but with the two cases where it's useful, it was unnecessarily
inconvenient: you first have to press `/` to open the filter prompt, and
then press enter to confirm the filter before you could press enter
again to trigger the chosen item. It's much easier to simply type to
filter, and still use the arrow keys to select one of the filtered
items, or press enter to trigger it while the filter prompt is showing.

The consequence of this is that while the keybindings menu is open you
can no longer use the displayed key bindings to trigger the commands; I
think that's fine, that menu is more for looking up those keybindings
rather than for using them from within the menu.

Also: since `j`/`k` are bound to move the list selection by default, it
is not possible to filter for something that begins with `j`/`k`. I
didn't want to change this because I'm concerned that die-hard vim users
would perceive it as a regression if they can no longer type `j` to
select the next menu item. The workaround is to type some other letter
and backspace; this keeps the filter prompt open, so you can now type
`j` or `k`.
2026-08-31 21:25:23 +02:00
Stefan Haller 90f5348371 Add a hint about filtering menus to the docs 2026-08-31 20:59:15 +02:00
Stefan Haller 4f78a5576a Reword stale comment about filtering not being available in the files view
This was implemented quite a while ago.
2026-08-31 20:59:15 +02:00
Stefan Haller 8aa57264fa Point the note about the menu's essential keys at what it means
There is no `reservedKeys` any more; the list of keys that menu items must
not shadow is `essentialKeys` in the function that creates the menu.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 4116a15dae Filter the recent repositories menu as you type
Picking a repository out of that list is the other place where the menu is
a list to search rather than a set of commands, and its items have no keys
that typing could clash with.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller bb7e74968b Drop the menu-specific wording for the filter prompt
The only menu that ever asked for it was the keybindings menu, which now
filters as you type and doesn't use the prompt at all. That leaves every
filterable context with the same prompt, so the whole hook can go, and
with it the two implementations that only existed to satisfy it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller fb761892aa Filter the keybindings menu as you type
Looking up a keybinding is a search, so the menu that lists them is the
one that most wants this. Its items do have keys, but only as a reminder
of what they do outside the menu, so nothing is lost by not binding them.

The prompt in front of the input field says what '@' does. It only ever
showed up while the user was typing in the search prompt, so it could
afford to be wordy; on a row that is on screen for as long as the menu
is, it can't.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 68355862c1 Add test helpers for a menu's filter row
Its footer, the hint in the menu's subtitle, where the row sits in
relation to the menu and the tooltip, and whether the text cursor is
showing are all things the tests for it need to look at.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 2048c7f0a6 Keep a filtering menu navigable whatever the keybindings are
The keys for paging through a menu are ',' and '.' by default, and there
is no non-printable alternative for them, so a menu that filters as you
type would lose paging altogether as soon as the user typed anything. The
same goes for confirming and cancelling if those keys are configured as
printable ones.

So bind the physical keys for all of it, on top of whatever is configured,
and only where they aren't the configured keys anyway.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller fe9b990c4c Filter a menu by typing into it
The filter input is where the keyboard points for as long as such a menu
is open, so that the first printable key can go straight into it. The menu
still gets every key the input doesn't take, because the input view is
embedded in the menu view, and the two are drawn as one focused panel.

Which keys the input takes changes once there is a filter: until then
printable keys still drive the menu, so that the configured navigation
keys work as usual, and afterwards they are all filter text. A menu item's
own keys are never bound in such a menu, because typing one has to reach
the filter rather than execute the item.

Escape gives up the filter and leaves the menu open; the next one closes
it. The filter prompt behind '/' is gone from these menus: the row already
does that job, and a second filter would only be confusing.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 1fb5f87d05 Lay out the filter row of a menu that filters as you type
The row is reserved for as long as such a menu is open, even while it is
still hidden, so that it can appear without moving the menu. That costs
two rows of the popup, which is why the screen has to be a little taller
before a menu is worth showing at all.

The prompt in front of the input field is dropped when the row gets too
narrow to type in, and the keybindings menu says what '@' does when it
still fits.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller c80035d7ee Add the views for a menu's filter row
Nothing shows or positions them yet. The row is two views because the
input field has to start after the "Filter:" prompt, and a gocui view is
a rectangle: the frame view draws the row and the prompt, the field sits
inside it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller f1b0347111 Allow a list to render its footer elsewhere
The footer is drawn on the bottom border of the list's view, which is not
always a free row: a panel that puts something else below the list shares
that border with it, and has to render the footer there instead.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller a9fe055af4 Extract applying a filter to a context
A filter can come from somewhere other than the search prompt: a menu
that filters as you type has its own input field, and needs to apply what
is typed there without going through the prompt's state.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 45d68bccc2 Treat the views of a popup panel as a group when clicking
The check was a single set of view names, so it also let a click move
between two different panels, e.g. from the prompt to the commit message.
List the panels instead, and require both views to be in the same one.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller f9ec7adb61 Draw embedded views as one focused unit
A view can only be drawn with the focused frame and title colors while it
is the current view, but a panel made of an outer view and an editable
field embedded in it has to look focused as a whole, whichever of the two
the keyboard is pointed at.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 9e98b3d2f3 Let an editable view opt into receiving printable keys as keybindings
Printable keys are withheld from keybindings while the user is typing in
a field, so that they end up as text. Decide that from the field that has
the focus rather than from the view a binding happens to be registered
for: a field can be embedded in another view, and that view's keys must
be withheld too, or its bindings would swallow the characters.

That makes it worth honouring KeybindOnEdit, which has been documented
but ignored ever since it was introduced. A field that sets it sees
printable keys offered to the keybindings first, and still gets them if
no binding handles them, which is what lets a view keep its keys until
the field has something to type into.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller 28c5f5748c Give a parent view's keybindings the same precedence as a view's own
When a key matches several bindings of the same view, the first one wins;
when it matches several of the view's parent, the last one did.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller b2a684bec1 Add a helper for recognizing printable keys
Two places test for "a character the user typed" by hand, and a third
one is about to be needed. Give the test a name.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:41:22 +02:00
Stefan Haller c840013ca3 Remove error return value from functions that always return nil
Originally I thought we'd benefit from this change in this branch; turns
out that we didn't after all, because we changed the approach, but it's
a nice cleanup anyway, so we include it here.
2026-08-31 20:41:22 +02:00
Stefan Haller 8924bca76f
Delete cpu.out file that was committed accidentally (#5983)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
2026-08-30 09:44:18 +02:00
Stefan Haller 07fa0ef732 Delete cpu.out file that was committed accidentally 2026-08-30 09:41:15 +02:00
Stefan Haller a8b762d03b
Improve performance of moving rebase todos (#5978)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
In larger repos, moving a rebase todo in an interactive rebase was
slower than necessary, especially when moving it up/down all the way
using auto-repeat.
2026-08-29 17:42:27 +02:00
Stefan Haller eddbbdad23
Avoid rehydrating unchanged rebase todos
Narrow rebase refreshes already have complete metadata for existing
todos. Reuse it so repeated todo moves do not spawn git show for the
entire list. New hashes still fall back to hydration.
2026-08-29 17:25:59 +02:00
Stefan Haller 3914755c98
Fix visual glitches when moving a rebase todo
This fixes two problems:
1. the list selection updated before the list content did, which caused
a bit of a wobble effect in the list
2. the main view would first update to a different commit's diff and
then back to the one that is being moved, resulting in a very ugly
flicker especially when moving the todo multiple times with auto repeat
2026-08-29 17:25:59 +02:00
Stefan Haller d37f901ac1
Remove PostRefreshUpdateKeepingScrollPosition
Now that we have PostRefreshUpdateWithOptions there's no reason to offer
a bespoke method for setting one particular option.
2026-08-29 17:25:59 +02:00
Stefan Haller fa531dc518
Add PostRefreshUpdateWithOptions 2026-08-29 17:25:59 +02:00
Stefan Haller fd1b229f93
Cleanup: remove CommitSelection option when refreshing rebase todos
This option has no effect, it isn't respected by refreshRebaseCommits,
so it looks misleading to include it here.
2026-08-29 17:25:59 +02:00
Stefan Haller b7ffa93dc6
Allow filtering worktrees by branch name in Worktrees pane (#5980)
Closes #5945.
2026-08-29 17:25:30 +02:00
Stefan Haller 530d773052 Allow filtering worktrees by branch name in Worktrees pane
Co-authored-by: phanirithvij <phanirithvij2000@gmail.com>
2026-08-29 17:02:44 +02:00
Stefan Haller c3027caf6b Cleanup: use lowercase for function parameters 2026-08-29 17:02:07 +02:00
Stefan Haller 655f913f44 Add test for filtering worktrees 2026-08-29 17:02:03 +02:00
Stefan Haller c300c319f9
Fix broken commit auto-scrolling (#5972)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.32.0, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.38.2, false) (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (2.44.0, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, false) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }} (latest, true) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
Fix a bug introduced in #5928: dragging a commit with auto-scrolling so
that the original commit leaves the viewport, and then dragging back
into the view would snap the original commit back into view.

Labeled as ignore-for-release because it's a regression in a PR that
wasn't released yet.
2026-08-27 09:55:00 +02:00
Stefan Haller 35753fd042 Fix scrolling back when dragging a commit
During auto-scrolling, turn off the automatic
scroll-to-make-the-selected-item-visible functionality of
PostRefreshUpdate.
2026-08-27 08:22:55 +02:00
Stefan Haller c8d610cb58 Demonstrate commit drag scroll reset on re-entry
When dragging a commit with auto-scrolling so that the original commit
leaves the viewport, dragging back into the view makes the original
commit snap back into view. This is a regression that was introduced by
aebf495dce.
2026-08-27 08:22:55 +02:00
Stefan Haller c09b682f63 Add some comments to the drag_to_reorder_with_autoscroll test 2026-08-27 08:22:55 +02:00
Stefan Haller 6a932f3057 Read mouse pointer geometry on the UI thread in tests
Drag autoscrolling deliberately keeps rendering after the test action
has returned, so view bounds can change while the test goroutine
prepares its next mouse event. Snapshot the geometry on the event loop
before translating view-relative coordinates to avoid data races. This
hasn't been a problem so far, but only because we were lucky; the added
test assertions later in this branch would cause consistent race
detector failures without this fix.
2026-08-27 08:22:55 +02:00
Stefan Haller ea91639546
Show renames when selecting a directory that a file was moved into or out of (#5924)
In a commit that moves a bunch of files from one directory to another,
showing the commit's files and selecting the target directory of those
moves would show these files as newly added rather than moved in the
main view's diff. Selecting the source directory would show them as
removed. Fix this to keep showing them as moved in both cases. The same
applies to the files panel when staging the move of a file, and when
filtering the file list down to just the source or target directory
using the `/` filter in either panel.

The decision to show them as renames when selecting the "moved-from"
directory was not an easy one; it's slightly weird because the list of
files in the side panel doesn't show them there (they appear in the
target directory), but the main view does. An alternative would have
been not to show them in that case, to match the side panel. However,
the point of selecting a directory is to see all the changes that affect
it, and the moved-out files are relevant changes you want to see there.

See
https://github.com/jesseduffield/lazygit/discussions/4899#discussioncomment-17976172.
2026-08-18 09:47:38 +02:00
Stefan Haller d7401559f0 Collapse the paths of a moved directory into the directory itself
A commit that moves an entire package elsewhere renames hundreds of
files, and passing every one of their old paths can push the command past
the length limit the OS imposes (~32k characters on Windows). Their
common parent directory does just as well whenever everything it holds
ends up in the diff anyway.

Deciding that needs to consider every file of the diff, not only those on
display, so the paths are now derived from the model rather than from the
tree; a status filter must not make a directory look emptier than it is.
2026-08-17 09:32:30 +02:00
Stefan Haller bee03d3b98 Show renames when diffing a directory that a file was moved into or out of
Git limits its tree diff by the pathspec before it looks for renames, so
a directory only ever gets one end of a rename whose other end is outside
it. Nothing is left to pair up, and the file turns into an addition or a
deletion that the commit doesn't contain.

Pass the other end along with the directory. This is bounded by the
number of renames that cross the directory's boundary, so it costs
nothing at all for the vast majority of commits.
2026-08-17 09:32:30 +02:00
Stefan Haller 40cb4bb24d Extract a single helper for the paths a node's diff is limited to
The files and commit files panels each had their own copy of this, one of
which used to be missing the previous path of a rename. Growing them
apart again is the last thing we want, since the next commit needs to
teach both of them about renames that cross a directory boundary.

The files panel version only returned paths for the filtered case, and
left it to WorktreeFileDiffCmdObj to derive the rest from the node; now
that all callers pass the paths in, that command doesn't need to know
about renames at all.
2026-08-17 09:32:30 +02:00
Stefan Haller 2c9187acb9 Pass the previous path when diffing a filtered directory in the files panel
Restricting the diff to the files that a filter leaves visible drops the
delete-side entry of a staged rename, so git shows the file as an
addition instead. Its commit files counterpart already passes both paths;
this brings the files panel in line.
2026-08-17 09:32:30 +02:00
Stefan Haller 6913f2afce Add tests for diffing a directory that files were renamed into or out of
Pathspec limiting happens before rename detection in git's tree diff, so
filtering the diff to a directory hides the delete-side entry of a rename
whose other end is outside that directory. Git then has nothing to pair
up, and reports a file moved into the directory as an addition and one
moved out of it as a deletion.

Selecting a directory is supposed to filter the commit's diff down, never
to change it, so both are wrong.
2026-08-17 09:32:30 +02:00
Stefan Haller c199ac69f5
Keep showing files whose conflicts have been resolved (#5940)
When several files have conflicts, resolving one of them makes it vanish
from the files panel as soon as it is auto-staged, and it only comes
back once the last conflict is resolved and the filter turns off again.
By then it sits among all the other changed files of the merge, so it is
hard to find the ones whose resulting diff you still wanted to check.

So remember which files had conflicts while the conflicted-files filter
is on, and keep showing them once they are resolved. This is the general
solution that #5936 called for; that PR only helped for the case of a
single conflicted file.

The consequence is that the selection no longer moves on to the next
conflicted file when one is resolved: it stays on the file you just
resolved, which shows you its diff right away.
2026-08-16 17:09:25 +02:00
Stefan Haller 43b47d16dd Keep showing files whose conflicts have been resolved
When several files have conflicts, resolving one of them makes it vanish
from the files panel as soon as it is auto-staged, and it only comes back
once the last conflict is resolved and the filter turns off again. By
then it sits among all the other changed files of the merge, so it is
hard to find the ones whose resulting diff you still wanted to check.

So remember which files had conflicts while the conflicted-files filter
is on, and keep showing them once they are resolved. This is the general
solution that 39513d244d called for; that commit only helped for the
case of a single conflicted file.

The consequence is that the selection no longer moves on to the next
conflicted file when one is resolved: it stays on the file you just
resolved, which shows you its diff right away.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 16:45:50 +02:00
Stefan Haller c10bc3b697 Collect the paths of the files with conflicts, rather than only counting them
The next commit needs to know which files have conflicts, not just how
many of them there are.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 16:45:50 +02:00
Stefan Haller adf59703ac AGENTS.md additions 2026-08-16 16:45:50 +02:00
Stefan Haller 431993b606
Bump golangci-lint to v2.12.2 (#5941) 2026-08-16 16:45:04 +02:00
Stefan Haller 37c53ac1bf Bump golangci-lint to 2.12.2 2026-08-16 16:35:11 +02:00
Stefan Haller 486d8536f2 Preallocate arrays where the new linter version would warn about it 2026-08-16 16:35:11 +02:00
Stefan Haller 92e50a5d9a Avoid appending to an array literal
Instead, create the one dynamic element beforehand and include it in the
literal. This avoids a preallocation warning from the linter.
2026-08-16 16:35:11 +02:00
Stefan Haller f0ccb937d3 Use lo.Map instead of manual append loops
Not only is this nicer code (and more idiomatic at least in this code
base), but it also avoids linter warnings about missing preallocations
(lo.Map does preallocate the result array).
2026-08-16 16:35:11 +02:00
Stefan Haller 1db9f9cdb8 Fix linter warning about WriteString(fmt.Sprintf(...)) 2026-08-16 16:35:11 +02:00
Stefan Haller a26899f5ab Fix use of reflect.Ptr
Apparently Ptr is a deprecated name; with the new golangci-lint version
this would cause

  inline: Constant reflect.Ptr should be inlined
2026-08-16 16:35:11 +02:00
Stefan Haller 4820241caf Remove the common-false-positives and legacy linter exception presets
I don't really know what they are for, but they don't trigger any
errors.
2026-08-16 15:44:36 +02:00
Stefan Haller 6032225472
Fix flicker, scroll glitches, and crashes in async diff rendering (#5938)
This is a preparation PR for the upcoming fold-staging-into-main-view
work; see the individual commit messages for details.

The most notable change is probably that we switch to a double-buffering
approach for flicker-free view updates; previously we would overwrite
the view from the top, and keep the existing viewlines below untouched
to update without flicker. This caused numerous problems though that
will become more painful when we start using the main view for more
operations (especially staging); telling whether the selected line still
belongs to the previous task or already to the new one is tricky.
Rendering into an offscreen buffer and swapping it in as soon as we have
enough to fill the screen makes this much easier.
2026-08-15 15:52:42 +02:00
Stefan Haller ebfa8c71b2 Drop FlushStaleCells, which no longer has anything to flush
It existed for the incremental re-render: a shorter render left the previous
one's view lines in the tail (deliberately, to avoid a blank frame), and this
cleared them once the new content was fully read. Async renders now build
off-screen and swap in whole, so refreshViewLinesIfNeeded truncates the view
lines to the buffer and no tail can form. All the call at end-of-input still
did was discard every wrapped line and force the whole buffer to be re-wrapped
on the next draw, which is pure work on a large diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 3cbf40d4ef Reset the scroll to the top at first paint, not when the task starts
When a main view re-renders content different from what it last showed, the
scroll resets to the top. That reset fired synchronously when the task started —
but with the off-screen render the previous content stays displayed until the
swap, so resetting the origin up front scrolled that still-visible content to the
top before the new content replaced it: a distracting jump when switching commits
(or any item) while scrolled down.

Defer the reset to the first paint that reveals the new content, so the previous
content stays at its scroll until the new content takes its place, and then the
new content appears at the top. Swap and reset happen in one hop on the UI
thread, so no draw can land between them and show the new content at the old
scroll. A same-content re-render keeps its scroll. The "loading..." indicator
path also resets the origin now, since it clears the previous content to show the
message and must put it at the top.

The reset moves out of NewTask into the read loop, keying off the flag that
already records whether the render's content is new. NewTask still decides,
from the same command-key comparison as before and under the same lock. It has
to be that flag rather than per-task state, because a task can be stopped and
replaced before it ever paints — a background refresh landing just after the
user clicked a different item, which is the ordering a VS Code terminal
produces, since it delivers the focus-in event (and so the refresh) before the
click. The replacement renders the same content and so sets nothing of its own,
and the click's reset would be lost with the task that owed it.

The manager's onNewKey callback is renamed resetOrigin to match its now-decoupled
timing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller bf6c34798c Don't run end-of-input handling for a render that was stopped
When a task is stopped to make way for a newer one, stopping closes
opts.Stop, and the scanner goroutine then closes lineChan. The read loop's
select between those two channels is therefore non-deterministic: it can
land on the closed lineChan (ok == false) instead of the opts.Stop case,
sending a stopped task into the end-of-input branch.

There it runs the full finalize — swapping its half-read off-screen buffer
in, clamping the origin to the truncated content, and clearing the loading
flag — all of which corrupt what the incoming task is about to render. The
most visible symptom is a brief frame of truncated content with the scroll
yanked to the top, seen when re-renders overlap rapidly (e.g. the periodic
background refresh re-rendering a main view faster than it can load, very
easy to hit under LAZYGIT_SLOW_RENDER).

The underlying bug predates the off-screen render (the EOF branch always
clamped the origin via onEndOfInput), but that change made it far worse by
also swapping a truncated buffer into the display. Fix it at the source: in
the EOF branch, check whether we were stopped and, if so, bail out like the
explicit stop case, leaving the view entirely to the task that replaces us.

There's no test because the bug is the non-deterministic select itself:
any test would have to win a coin flip to observe it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 9e23111172 Render async content into an off-screen buffer and swap it in
A cmd/pty re-render used to overwrite the displayed buffer from the top
down as lines arrived, relying on keeping the previous render's view-line
tail to avoid a blank frame. That left the view showing a mixture of old
and new content while loading, and any reader (draw, clicks, the
view-line mapping) could observe a half-written buffer at the wrong
scroll.

Instead, build the new content in a second, off-screen viewBuffer: until
the task has read enough to paint, writes go there and the displayed
buffer — and so everything every reader sees — is left untouched. Once the
task reaches its first-paint point (InitialRefreshAfter, or EOF for short
content) it swaps the off-screen buffer in atomically, so the view jumps
straight from the previous render to the new one with no intermediate
frame. Subsequent lines append to the now-displayed buffer.

Swapping at the first-paint point means the displayed buffer is only a
viewport tall when it appears and then grows as the rest streams in toward
the count needed for an accurate scrollbar. The scrollbar is sized from the
displayed buffer's height, so left to itself the thumb would shrink and
snap back during that growth (most visibly: the files panel's periodic
refresh making the thumb jump while scrolled down). The total height the
scrollbar needs is a strictly later quantity than the viewport-fill paint,
so no single early swap can have both right. FreezeScrollbarHeight therefore
records the view's height when a load begins and the scrollbar is held there
— growing only if the new content turns out taller — until the load ends; a
synchronous render superseding the load releases it. This mirrors the layout
clamp, which already ignores the partial content height while a view loads.

With the swap doing a wholesale replace, refreshViewLinesIfNeeded can
truncate the view lines to the current buffer: there is no longer a
half-loaded shorter buffer whose tail we must keep showing, so a stale
tail never forms. clear()/Reset() abandon any in-progress off-screen
render so a synchronous SetContent after a stopped task writes to the
display.

The swap holds writeMutex for now; it could later move to the main thread.
Flicker behaviour still needs interactive verification (LAZYGIT_SLOW_RENDER
+ a real diff renderer).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 87b30d9581 Don't take the view over for "loading..." when the content isn't changing
A render that takes more than 200ms to produce its first line takes the view
over to say "loading...", which clears the buffer it was showing. That is
worth doing when the content coming is different — the view is showing
something the user has moved on from, and saying so beats leaving it there
silently. It is pure flicker when the content isn't changing: the view is
already showing exactly what the render is about to put back, and a slow
re-render of unchanged content is common (a background refresh over a repo
with submodules that have uncommitted changes, say).

So track whether the render in flight has content the view isn't already
showing, and only let the indicator take over when it does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller cc5d5057a7 Make the buffer-writing methods operate on a viewBuffer
write, writeCells, makeWriteable, parseInput and
autoRenderHyperlinksInCurrentLine produced cells into v.buf; move them onto
viewBuffer so they can write into any buffer, not just the displayed one.
The display-side effects that don't belong to content production —
tainting, clearing hover, updating search positions — stay behind in the
View.write wrapper, which delegates the actual writing to v.buf.write(v).
Render config the writer needs (Editable, colors, width, tab width,
hyperlink auto-render) is read from the passed View. Behaviour-preserving:
the wrapper still always targets v.buf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 2a6cb8d78e Bundle a view's cell buffer and write state into a viewBuffer
The fields that make up a view's content and the act of writing to it —
the cell buffer (lines), the write cursor (wx/wy), the escape-sequence
decoder (ei) and the held-newline flag (pendingNewline) — were loose
fields on View. Bundle them into a viewBuffer struct that View holds by
pointer. This is a behaviour-preserving prep refactor: every access just
goes through v.buf now. It sets up rendering into a second, off-screen
viewBuffer that can be swapped in atomically, so an async re-render never
exposes a half-written buffer to readers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 114d18dc04 Reset other main views' scroll after copying content, not before
refreshMainViews reset the scroll position of every other main view at the
very top, before moveMainContextPairToTop runs its CopyContent. CopyContent
copies the previously-shown view's content into the now-visible one to avoid a
blank frame during the async re-render — but because the reset ran first, it
had already zeroed the origin of that soon-to-be-copied source view. The
placeholder therefore always appeared scrolled to the top, jumping away from
wherever the screen actually was, on every cross-pair transition.

Move the reset to after the copy. The end state is unchanged (each other main
view still ends at origin 0, and the destination always re-renders), but the
brief placeholder now stays at the source view's real scroll position until
the real content paints.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller fbadbbf99a Don't scroll a view up to fill blank space while its content is loading
The layout scrolls a view up if its origin is past the bottom of its
content, to avoid showing blank space (e.g. after a resize). But it measures
content height by the lines loaded so far, and command/pty tasks load
asynchronously. So when a view is re-rendered while scrolled down, the layout
would yank it to the top because only a fraction of the content has been read
yet, then leave it there once loading finished.

Track whether a command task is actively reading (set synchronously when the
task is created, so a layout pass in between sees it; cleared at EOF, but not
when stopped, since that means a newer task is taking over) and skip the
scroll-up clamp for such views. onEndOfInput already re-clamps once loading
completes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 85cff19a2a Fire queued ReadToEnd callbacks when the initial read reaches EOF
A task's read loop processes one LinesToRead request at a time. The initial
request has a large line count and no Then callback; if the content is shorter
than that, the loop hits EOF on the initial request and breaks out, abandoning
any further requests still sitting in the readLines channel. So a ReadToEnd
call that races a still-loading-but-shorter-than-its-initial-read view has its
Then silently dropped: it isn't fired immediately (the channel was non-nil at
call time) and it's never dequeued.

On EOF, drain the queued requests and fire their Then callbacks before
breaking out, since reaching EOF trivially satisfies any "read more" request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 9293f03c83 Add a test for a read request queued while a task reaches EOF
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 86c9e6a20a Lock the view while reading viewLines on the event-handling thread
hyperlinkAt (the click path) and onMouseMove/findHyperlinkAt (hover) read
v.viewLines without holding writeMutex, unlike every other reader. They run
on the event-handling goroutine, so a re-render on the task goroutine can
shrink or rebuild viewLines between the bounds check and the indexing,
causing an out-of-range panic (observed: "index out of range [60] with
length 0" while hovering during a diff re-render).

Take writeMutex for the duration, like the other viewLines readers do, so
the check and the access see the same slice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller e3fe321080 Move the click-path hyperlink lookup onto View
Reading a view's internal buffer belongs on the view itself, next to
findHyperlinkAt, rather than in the event loop; and the view is where the
lock that guards that buffer can be taken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller d65ee95942 Add LAZYGIT_SLOW_RENDER debug knob for watching async render frames
Re-rendering a diff into a main view is asynchronous and lazy: the read
loop fills the view a screenful at a time and refreshes as it goes. When
debugging scroll-restore and flicker behaviour, the individual frames go by
too fast to see. Setting LAZYGIT_SLOW_RENDER=<milliseconds> sleeps that long
after each line is written, stretching the load out so the frames become
visible. It has no effect when unset, so it's safe to leave in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 94018de3e8 Route all view origin writes through SetOriginX and SetOriginY
Several methods assigned v.ox and v.oy directly: SetOrigin, CopyContent,
the wrap/autoscroll branches in draw, FocusPoint, and
Scroll{Up,Down,Left,Right}. Funnelling them all through SetOriginX and
SetOriginY gives a single place to observe (or set a breakpoint on)
every change to a view's scroll position, which makes debugging scroll
behaviour much easier.

This means those call sites now also get the setters' `< 0` clamps, but
that is behaviour-preserving in every case: each assigned value is
already >= 0. calculateNewOrigin never returns a negative number;
CopyContent copies origins that are themselves always >= 0; and the draw
and scroll writes are all guarded (or fed only non-negative amounts) so
the result can't go below zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 01600042cd Guard taskKey with the mutex that already guards the task ID
taskKey is written on the goroutine NewTask spawns, under taskIDMutex, but
GetTaskKey read it without the lock — and the string renders in
tasks_adapter.go call that from the UI thread while a previous task's
goroutine may be writing. A Go string is a two-word value, so a torn read
can pair one string's pointer with another's length and index out of
bounds, not merely return the wrong key.

Take the lock in GetTaskKey, and read the field directly at the one call
site that already holds it.

No test: the failure needs two goroutines to interleave inside a
two-word assignment, which nothing can schedule deterministically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 15:30:27 +02:00
Stefan Haller 1868167a7a
Auto-select the conflicted commit when stopping in rebase (#5937)
When a rebase (or multi-commit cherry-pick or revert) stops with a
conflict, it is often useful to look at the diff of the "<-- CONFLICT"
commit to double-check that the conflict resolution matches the diff of
the original commit. To make that easier, select that commit
automatically.
2026-08-15 15:29:31 +02:00
Stefan Haller 1c17ee1a92 Auto-select the conflicted commit when stopping in rebase
When a rebase (or multi-commit cherry-pick or revert) stops with a
conflict, it is often useful to look at the diff of the "<-- CONFLICT"
commit to double-check that the conflict resolution matches the diff of
the original commit. To make that easier, select that commit
automatically.
2026-08-15 15:24:36 +02:00
Stefan Haller 6417da5319 Add selection assertions to tests involving conflicts
This doesn't change anything, we just pin down the selection behavior
around conflicts; we are going to change that behavior, and these tests
will make it obvious how when they change in the next commit.
2026-08-15 15:24:15 +02:00
Stefan Haller aa38daff98
Keep the last conflict file selected after resolution (#5936)
When there is a single conflicting file left to be resolved, lazygit
dismisses the conflicted-files-only filter when the file no longer has
conflict markers. However, the selection moved to the top, which is
annoying because very often it is useful to look at that file's
resulting diff once more to confirm that conflicts were resolved
correctly, and finding it again can be cumbersome when there are many
changed files. So keep it selected.

Of course, this only helps for the last (or only) conflicted files; when
there are multiple, a resolved file disappears from the panel until all
are resolved, which makes it hard to double-check the resulting diffs.
Doing it afterwards is not easy because you'd have to remember which
files were conflicting. This needs a different solution, but for the
special case of only a single conflicting file this is already a big
improvement.
2026-08-15 15:24:01 +02:00
Stefan Haller 39513d244d Keep the last conflict file selected after resolution
When there is a single conflicting file left to be resolved, lazygit
dismisses the conflicted-files-only filter when the file no longer has
conflict markers. However, the selection moved to the top, which is
annoying because very often it is useful to look at that file's
resulting diff once more to confirm that conflicts were resolved
correctly, and finding it again can be cumbersome when there are many
changed files. So keep it selected.

Of course, this only helps for the last (or only) conflicted files; when
there are multiple, a resolved file disappears from the panel until all
are resolved, which makes it hard to double-check the resulting diffs.
Doing it afterwards is not easy because you'd have to remember which
files were conflicting. This needs a different solution, but for the
special case of only a single conflicting file this is already a big
improvement.
2026-08-15 15:03:29 +02:00
Stefan Haller 4e2a1cd5b0 Add a function SetStatusFilterPreservingSelection 2026-08-15 14:58:27 +02:00
Stefan Haller b2b9519bcc Extract a private helper function preserveSelection
The operation around which the selection should be preserved is passed
in so that it can be reused for different purposes.
2026-08-15 14:58:10 +02:00
Stefan Haller a1f1a4ee8c
Draw the UI in a more inactive look when the window is not focused (#5935)
When using lazygit in a multi-tab terminal it is useful to see if the
lazygit tab is currently active; ghostty does a very good job at dimming
down the inactive tabs, but VS Code's builtin terminal does not, so
indicate this on our side by removing the green highlight from panel
frames and tab titles, and showing the selection as inactive like we do
for a side panel when the main view is focused.
2026-08-15 12:39:51 +02:00
Stefan Haller a5a2bd0699 Draw the UI in a more inactive look when the window is not focused
When using lazygit in a multi-tab terminal it is useful to see if the
lazygit tab is currently active; ghostty does a very good job at dimming
down the inactive tabs, but VS Code's builtin terminal does not, so
indicate this on our side by removing the green highlight from panel
frames and tab titles, and showing the selection as inactive like we do
for a side panel when the main view is focused.
2026-08-15 12:17:40 +02:00
Stefan Haller ae1007612b
Improve startup time (#5934)
This PR has two separate improvements for the startup time, in
particular for the time until the Files panel shows the modified files:

- avoid walking the `.git/workspaces` dir recursively, looking for the
gitdir files of linked worktrees. This code was not used to populate the
worktrees panel, but only for the decision in the Files panel whether to
show an entry with the worktree icon; it was doing unnecessary work,
because walking the `.git/workspaces` dir recursively is pointless (git
stores the worktree gitdir files only at the top level, so a flat read
of that directory would have been enough), and can take significant time
in large repos, especially when they have many submodules. Instead of
fixing that code, remove it entirely and rearrange the refresh code so
that we can use the regular worktree model for this Files panel
decision.
- at startup we were doing two full refreshes at the same time: the
regular one that we always do after loading a repo for the first time,
and then also a focus-in refresh. I didn't realize that a terminal will
send us a focus-in event right at the moment we request these events.
Nothing bad happens from doing those two refreshes at the same time, but
it slows things down a bit when we have two "git status" calls running
concurrently.

Both of these together reduce the time it takes for the Files panel to
show its files at startup (in my regular work repo with three worktrees
and ~30 submodules) from 860ms to 440ms, so almost a factor of two. If
you want to measure this in your own repo, here's a small throwaway
patch that you can use for that:

```diff
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index 40ce86eae..fbb482f39 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -26,6 +26,11 @@ import (
 	"github.com/sasha-s/go-deadlock"
 )
 
+var (
+	applicationStartTime     = time.Now()
+	applicationStartTimeOnce sync.Once
+)
+
 type RefreshHelper struct {
 	c                    *HelperCommon
 	refsHelper           *RefsHelper
@@ -1213,6 +1218,10 @@ func (self *RefreshHelper) refreshFilesAndSubmodules(captured capturedFilesState
 	self.refreshView(self.c.Contexts().Submodules, env)
 	self.refreshView(self.c.Contexts().Files, env)
 
+	applicationStartTimeOnce.Do(func() {
+		self.c.Log.Infof("Time until first files refresh: %s", time.Since(applicationStartTime))
+	})
+
 	return nil
 }
 
```

To use it, run `./lazygit -l | grep "first files refresh"` in one
terminal, and `lazygit -d` in the one that you want to test. I'm curious
about your before/after measurements, feel free to post them below in
the comments.
2026-08-15 12:02:33 +02:00
Stefan Haller 312a5f2cc1 Only react to focus reports that change whether we're focused
A terminal that supports focus reporting answers with the state it is
already in when we turn reporting on, so at startup we were told that we
had gained focus that we never lost, and refreshed everything a second
time on top of the refresh that loading the repo had just started. The
two ran at once, each with its own `git status`, which made both of them
slower than the one refresh needed to be.

Keep track of what the reports say, then, and pass on only the ones that
change it. Assuming that we start out focused costs us nothing when we
don't: that same first report says so, so a lazygit started in a window
that isn't in front knows it from the start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:26:58 +02:00
Stefan Haller 80ad2db71a Recognize worktrees among the files from the worktrees model
Finding out which of the files are worktrees of ours had its own answer
to where this repo's worktrees are, walking the directory that git keeps
them in. The worktrees panel asks git itself, and that is the better
answer: it is the one git gives for the same question elsewhere in the
app, and it doesn't need to know where git records what.

The model that panel fills is all the files need, so mark them from it.
That takes the work out of the file loader, whose other two callers were
paying for it without wanting it, and it costs no git call at all: both
models are written on the UI thread, so whichever of the two refreshes
lands second marks the files against the other's fresh data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:26:58 +02:00
Stefan Haller a1f5db6bce Add test for how we show a worktree that is inside the main work tree
We show this with a worktree icon (which is only shown when nerd fonts
are used, so turn these on), but also we strip the trailing `/` that
"git status" reports, so that it shows as a file rather than a directory
with a bogus file in it.

The reason for adding the test is that we are going to touch the logic
that determines whether an item in the Files panel is a linked worktree,
and this guards against regressing.
2026-08-15 11:26:58 +02:00
Stefan Haller 62caf427ac Refresh the worktrees in their own scope again
The worktrees were loaded and written by the branches refresh whenever
both were in scope, because the branches view shows worktrees against
branches: refreshing them separately rendered that view twice, once
with worktrees that were still stale.

Ordering the two is enough for that, and it leaves each scope owning
its own model again. The worktrees refresh now runs first and queues
its model write before it reports being done, so a branches refresh
that waits for it queues its own write behind that one, and renders
once with both. The worktrees scope only renders the branches view
itself when nobody else is going to.

As a side effect the two loads now run concurrently, where the branches
refresh used to load the worktrees after its own branches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:18:23 +02:00
Stefan Haller cfe7961d54 Give commits and branches their own scope checks
Everything in performRefresh is meant to read as "if this scope was
asked for, refresh it", with the scopes that always change together
expanded into each other up front so that each check can name a single
one. The commits and the branches were the exception: one condition
asking for either of them refreshed both, so what that block does only
followed from reading it together with the expansion at the top of the
function. The rebase commits hung off the same condition as an else,
even though it is the commits refresh they are an alternative to.

Expand those two into each other like the other pairs, and give each of
them a check of its own. They now capture their inputs separately,
which is what every other scope has always done.

The reflog stays with the branches rather than getting a check of its
own, because sorting the branches by recency needs it loaded first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:18:23 +02:00
Stefan Haller 1f1fd99357
Two small improvements to the debug log (#5933)
- log time stamps with nano-second granularity (on macOS we effectively
get only microseconds, but that's still more than we need); `lazygit -l`
prints them with millisecond resolution, which can be useful for
investigating performance
- add a blank line at startup to make it much easier to see where a new
run starts
2026-08-15 09:53:13 +02:00
Stefan Haller 0140e19eb2 Separate the runs in the debug log with a blank line
This makes it much easier to find where a new run starts; previously you
had to search for `git --version` for that.
2026-08-15 09:48:11 +02:00
Stefan Haller d298a7ce4b Use nano-second time stamp granularity in the debug log
It's overkill for most purposes, but I'd say it doesn't hurt to have the
extra resolution available in the raw JSON data for the few cases where
it's useful. Have "lazygit -l" print them with milli-second resolution;
that seems to be a good middle ground.
2026-08-15 09:47:32 +02:00
Stefan Haller 1248ac4457
Disable staging all files when there are none to stage (#5932)
Pressing `a` in the files panel to stage all files would show a
confusing error popup about something with submodules, which made no
sense at all. And worse, when pressing `a` very quickly after startup
(before the initial refresh had a chance to populate the files panel) it
would crash with a nil pointer panic.

Fix both by showing an error toast that there are no files to stage.

Fixes #5929.
2026-08-15 09:47:16 +02:00
Stefan Haller cbc3da507b Disable staging all files when there are none to stage
Besides the misleading error about submodules, the command crashes when
it runs before the first files refresh has come in: the file tree
doesn't exist yet at that point, and staging all of a tree that isn't
there dereferences a nil root node. That is easy to hit in a big repo,
where `git status` takes a moment while the panel sits there empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 22:52:45 +02:00
Stefan Haller 0c04ee5c61 Add a test for staging all files when there are none
The stage-all command acts on the whole file tree, and nothing stops it
from doing that when the tree is empty. It ends up in the branch that
explains why a submodule couldn't be staged, which has nothing to do
with what the user did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 22:52:45 +02:00
Stefan Haller c477a2959b
Avoid gofumpt'ing other worktrees (#5931)
This is usually not a problem (except for the unnecessarily wasted time)
because all worktrees should normally be correctly formatted; but it is
a problem when we bump the formatter version, and some worktrees are
already on the new version and others still on the old.
2026-08-14 22:51:59 +02:00
Stefan Haller 1a3cf798f9 Avoid gofumpt'ing other worktrees
This is usually not a problem (except for the unnecessarily wasted time)
because all worktrees should normally be correctly formatted; but it is
a problem when we bump the formatter version, and some worktrees are
already on the new version and others still on the old.
2026-08-14 22:43:37 +02:00
Stefan Haller af04698d9a
Scroll the selection into view by default again (#5928)
In v0.58 (see #5134) we changed the refresh behavior to no longer scroll
a selection into view by default if it had been scrolled out of view
using the mouse wheel; the primary reason for that was to avoid a
background fetch or files refresh to yank the selection back into view
while you are looking at something else, which was pretty annoying.
However, that meant we had to fix lots of cases where the selection
didn't become visible after a normal, foreground user action, and add
code to manually scroll it into view again in those cases. This is
error-prone and easy to forget for new features, and to this day we were
still missing some.

So turn it around: by default, the selection is scrolled into view
again, and the few cases where we don't want it (background routines and
the focus-in refresh) opt out.

This does mean that some user actions now scroll into view that didn't
before, and there might be cases where this is unwanted (I can't think
of one, but who knows). If we come across one we can easily fix it by
opting out; this is probably still much better than not scrolling into
view where it's wanted.
2026-08-14 08:31:46 +02:00
Stefan Haller 5c4c7139d7 Remove the scroll calls that are now redundant
Every one of these did by hand what focusing the list now does on its
own: five hand-added scroll requests, and four origin resets that paired
a "select the first item" with a "and show the top of the list".

The scroll that the commits refresh performed when it found the selected
commit at a new index goes too. It is now unconditional for a foreground
refresh, and deliberately absent for a background one: when an agent
commits in another window, we would rather see the new commits arrive
than have the view yank itself back to the commit we had selected.

The one origin reset that stays is the one in ReApplyFilter, which runs
as part of a refresh and so can't rely on the refresh scrolling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller aebf495dce Scroll the selection into view by default
Ever since scrolling the selection into view became opt-in, we have been
fixing the same class of regression by hand, five times so far: a
controller moves the selection somewhere new, doesn't say that it wants
the view to follow, and the selection ends up off screen. The decision
needs facts from two places — whether the selection went somewhere new is
known to the list, whether the scroll position is the caller's to manage
is known to the caller — and asking every caller for both is what keeps
going wrong. The callers that get it wrong are usually not even the ones
that moved the selection: they are pass-throughs like postRefreshUpdate,
which can't know what a refresh did to the selection.

So default to scrolling, and let the two callers that maintain the scroll
position themselves say so.

The one case where scrolling is always wrong is a refresh that no user
action is behind: a background poll, or a reload of state on window
focus, after a subprocess, or after a repo switch. Those must leave the
viewport wherever the user last scrolled it to — that is what made the
scrolling opt-in in the first place. Both are already marked in
RefreshOptions, so the refresh can decide it once, centrally, instead of
each caller judging it.

A user action that ends in a foreground refresh does now yank the view
back to the selection if the user had scrolled away from it. That's a
behaviour change, and there may be actions where it turns out to be
unwelcome; those we can fix individually, and it beats the ones that
don't scroll today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller 29d05e23f9 Add tests for the scroll-into-view regressions we fixed by hand
Since scrolling the selection into view became opt-in, five places have
had to be fixed by hand after the fact, none of them with a test. Cover
them now: making the scrolling automatic has to keep all five working,
and once it does, the hand-added scroll calls can go.

Two of them assert that the selection is visible rather than on an exact
scroll position, because the panel they look at changes height along the
way (filtering mode switches to half screen), or because what matters is
only that the commit we jumped to can be seen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller a85d6e0349 Add a test for dragging a range selection past the bottom of a panel
This is the other place that manages its own scroll position: while a
drag extends the selection to a line below the viewport, the view stays
put, and the drag autoscroller scrolls it one line at a time for as long
as the pointer stays there. Making the scroll automatic would centre the
selection instead, i.e. jump the view rather than scroll it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller e9faf0325d Add a test that a background refresh keeps the scroll position
The one behaviour that made scrolling the selection into view opt-in in
the first place — a background refresh must not yank the view back to a
selection the user scrolled away from — has never been covered by a test.
It's about to become the one case that the automatic scrolling has to
suppress, so cover it first.

Getting there needs two things from the test harness: mouse wheel events,
which are the only way to scroll a list panel without moving the
selection, and a way to trigger a background refresh. The periodic
routine that issues it is turned off in tests, and turning it on would
mean waiting for its timer and hoping it fires while we're looking, so
drive the refresh directly instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller a881fb7ee0 Add a test for paging up and down in a list
We are about to make list panels scroll their selection into view
automatically. Page up and down are one of the few places that manage
the scroll position themselves, keeping the selection at the edge of the
viewport rather than in its middle, and nothing covers that today.

Asserting on it needs an exact scroll position assertion; only
OriginYAtLeast existed so far.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00
Stefan Haller 4e4cd93993
Fix example for args field in rawGit diff renderer docs (#5927) 2026-08-14 07:45:17 +02:00
Stefan Haller ac5e0b4ae9 Fix example for args field in rawGit diff renderer docs 2026-08-14 07:42:25 +02:00
Stefan Haller d25315b55a
Bump gofumpt to version 0.11.0 (#5925) 2026-08-13 20:51:19 +02:00
Stefan Haller 25c6aeadf0 go get -tool mvdan.cc/gofumpt@v0.11.0 2026-08-13 20:40:11 +02:00
Stefan Haller b1e9ac3969 Remove a few unnecessary parentheses
The new version of gofumpt that we are going to update to in a moment
would complain about these.
2026-08-13 20:40:11 +02:00
Stefan Haller fbe2379fa5
Bump JamesIves/github-sponsors-readme-action from 1.6.0 to 1.6.1 (#5917)
Bumps
[JamesIves/github-sponsors-readme-action](https://github.com/jamesives/github-sponsors-readme-action)
from 1.6.0 to 1.6.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jamesives/github-sponsors-readme-action/releases">JamesIves/github-sponsors-readme-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.6.1</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Dependencies 🤖</h3>
<ul>
<li>chore(deps): bump the misc group across 1 directory with 3 updates
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/JamesIves/github-sponsors-readme-action/pull/1034">JamesIves/github-sponsors-readme-action#1034</a></li>
<li>chore(deps-dev): bump <code>@​types/node</code> from 25.2.0 to
25.2.1 in the misc group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/JamesIves/github-sponsors-readme-action/pull/1036">JamesIves/github-sponsors-readme-action#1036</a></li>
<li>chore(deps): bump actions/setup-node from 6.2.0 to 6.3.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/JamesIves/github-sponsors-readme-action/pull/1041">JamesIves/github-sponsors-readme-action#1041</a></li>
<li>chore(deps): bump actions/upload-artifact from 6.0.0 to 7.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/JamesIves/github-sponsors-readme-action/pull/1040">JamesIves/github-sponsors-readme-action#1040</a></li>
<li>chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/JamesIves/github-sponsors-readme-action/pull/1052">JamesIves/github-sponsors-readme-action#1052</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/JamesIves/github-sponsors-readme-action/compare/v1...v1.6.1">https://github.com/JamesIves/github-sponsors-readme-action/compare/v1...v1.6.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="02650b8cd4"><code>02650b8</code></a>
Merge remote-tracking branch 'origin/dev' into releases/v1</li>
<li><a
href="e958d3e9b7"><code>e958d3e</code></a>
Deploy Production Code for Commit
d0e97b6c881bf5598173fe04a41bffac988b7667 🚀</li>
<li><a
href="c0481077d6"><code>c048107</code></a>
Merge remote-tracking branch 'origin/dev' into releases/v1</li>
<li><a
href="8fd9552b1c"><code>8fd9552</code></a>
fix: use a dedicated RELEASE_PAT for release creation</li>
<li><a
href="d0e97b6c88"><code>d0e97b6</code></a>
Merge branch 'dev' of <a
href="https://github.com/JamesIves/github-sponsors-readme-act">https://github.com/JamesIves/github-sponsors-readme-act</a>...</li>
<li><a
href="6eb9fb19bd"><code>6eb9fb1</code></a>
ci: run sponsors README update twice a week instead of daily</li>
<li><a
href="152fad67d2"><code>152fad6</code></a>
ci: run integration tests weekly instead of daily</li>
<li><a
href="7962d89b08"><code>7962d89</code></a>
chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (<a
href="https://redirect.github.com/jamesives/github-sponsors-readme-action/issues/1052">#1052</a>)</li>
<li><a
href="7b03cded5b"><code>7b03cde</code></a>
fix: remove unsupported semver cooldown keys from the github-actions
ecosystem</li>
<li><a
href="592de7d30b"><code>592de7d</code></a>
security: add explicit permissions blocks to workflows</li>
<li>Additional commits viewable in <a
href="2fd9142e76...02650b8cd4">compare
view</a></li>
</ul>
</details>
<br />
2026-08-12 19:45:58 +02:00
dependabot[bot] f77c1d37f1
Bump JamesIves/github-sponsors-readme-action from 1.6.0 to 1.6.1
Bumps [JamesIves/github-sponsors-readme-action](https://github.com/jamesives/github-sponsors-readme-action) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/jamesives/github-sponsors-readme-action/releases)
- [Commits](2fd9142e76...02650b8cd4)

---
updated-dependencies:
- dependency-name: JamesIves/github-sponsors-readme-action
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-12 17:43:40 +00:00
Stefan Haller 27fcf4b729
Bump github.com/lucasb-eyer/go-colorful from 1.4.0 to 1.4.1 (#5916)
Bumps
[github.com/lucasb-eyer/go-colorful](https://github.com/lucasb-eyer/go-colorful)
from 1.4.0 to 1.4.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/lucasb-eyer/go-colorful/releases">github.com/lucasb-eyer/go-colorful's
releases</a>.</em></p>
<blockquote>
<h2>v1.4.1</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: correct D50ToD65 to the CSS Color 4 matrix that inverts
D65ToD50 by <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a> in <a
href="https://redirect.github.com/lucasb-eyer/go-colorful/pull/85">lucasb-eyer/go-colorful#85</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gaoflow"><code>@​gaoflow</code></a> made
their first contribution in <a
href="https://redirect.github.com/lucasb-eyer/go-colorful/pull/85">lucasb-eyer/go-colorful#85</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/lucasb-eyer/go-colorful/compare/v1.4.0...v1.4.1">https://github.com/lucasb-eyer/go-colorful/compare/v1.4.0...v1.4.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/lucasb-eyer/go-colorful/blob/master/CHANGELOG.md">github.com/lucasb-eyer/go-colorful's
changelog</a>.</em></p>
<blockquote>
<h2>[1.4.1] - 2026-08-02</h2>
<h3>Fixed</h3>
<ul>
<li>Corrected <code>D50ToD65</code> to use the CSS Color 4 matrix
inverse of <code>D65ToD50</code> (<a
href="https://redirect.github.com/lucasb-eyer/go-colorful/issues/85">#85</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="315b48282c"><code>315b482</code></a>
ready for v1.4.1</li>
<li><a
href="e9123175c0"><code>e912317</code></a>
fix: correct D50ToD65 to the CSS Color 4 matrix that inverts
D65ToD50</li>
<li>See full diff in <a
href="https://github.com/lucasb-eyer/go-colorful/compare/v1.4.0...v1.4.1">compare
view</a></li>
</ul>
</details>
<br />
2026-08-12 19:41:33 +02:00
dependabot[bot] 93b8f343bb
Bump github.com/lucasb-eyer/go-colorful from 1.4.0 to 1.4.1
Bumps [github.com/lucasb-eyer/go-colorful](https://github.com/lucasb-eyer/go-colorful) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/lucasb-eyer/go-colorful/releases)
- [Changelog](https://github.com/lucasb-eyer/go-colorful/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lucasb-eyer/go-colorful/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/lucasb-eyer/go-colorful
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-12 17:38:40 +00:00
Stefan Haller c18a0c6680
Fix pull requests silently disappearing until lazygit is restarted (#5921)
A lazygit instance that has been open for a while sometimes stops
showing pull requests, and keeps not showing them until you quit and
restart it. The cause is that we resolve the GitHub token once per
process and then keep using that stale answer.

We get the token from go-gh, which reads gh's `hosts.yml` on the first
call and caches it for the lifetime of the process. gh rewrites that
file whenever the active account changes, and keeps the active account's
token either in the file or in the system keyring, depending on the
account. Once the file changes under us, our snapshot no longer
describes reality: either we keep sending a token for an account that is
no longer active, or — if the snapshot was taken while a keyring-backed
account was active — we find no token at all, drop the remote, and show
no pull requests. Nothing is reported to the user, so it looks like PR
fetching is simply broken.

Switching accounts with `gh auth switch` is the easiest way to trigger
this, but it isn't limited to multi-account setups: a single account
hits the same thing whenever its token is rotated, or moved between the
keyring and `hosts.yml` by a fresh `gh auth login`.

So ask gh itself for the token on every refresh, with `gh auth token
--hostname <host>`, which resolves it afresh from whichever of the
environment, the keyring, or the config file currently holds it. Not
passing `--secure-storage` (which go-gh does internally) also means it
stops mattering which of the two the active account uses. go-gh's lookup
stays as a fallback for setups without the gh binary, where it still
picks up `GH_TOKEN` and friends; when gh is present it checks those
variables itself.

To reproduce on master, with two gh accounts, one with its token in the
keyring and one in `hosts.yml`: start lazygit while the keyring-backed
account is active, then run `gh auth switch` to the other one. The pull
request information disappears on the next refresh and doesn't come back
until lazygit is restarted. Running `gh auth token --secure-storage
--hostname github.com` by hand at that point prints `no oauth token
found for github.com`.
2026-08-12 19:30:37 +02:00
Stefan Haller c8bc1928f2 Get the GitHub token from gh instead of resolving it in-process
go-gh reads gh's config file once per process and answers from that
snapshot for the rest of the process's life. gh rewrites the file
whenever the active account changes, and stores the active account's
token either in it or in the system keyring, depending on the account.
A lazygit that has been running for a while therefore consults a
snapshot that no longer describes reality: it either keeps using a
token for an account that is no longer active, or, when the snapshot
was taken while a keyring-backed account was active, finds no token at
all and silently stops showing pull requests until it is restarted.

Asking gh resolves the token afresh on every refresh, from whichever of
the environment, the keyring or the config file currently holds it.
go-gh's lookup stays behind as a fallback for setups without the gh
binary, where it still picks up GH_TOKEN and friends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:13:06 +02:00
Stefan Haller 4b22b844e7
Fix hang on quit when confirmOnQuit is true (#5919)
When confirmOnQuit is true, quitting would sometimes hang for three
seconds and then print "cannot kill child process". Concretely, this
happened whenever the Files panel was focused but there were no changed
files (the main view shows "No changed files").

This is a regression in 0.64.0, it worked before.

Fixes #5918.
2026-08-12 19:12:16 +02:00
Stefan Haller ec577f1afa Give up waiting for the UI thread once the main loop has exited
Quitting with confirmOnQuit set hung for three seconds and printed
"cannot kill child process", but only with a clean working tree. Closing
the confirmation pops the context before running its handler, so the
files panel is re-focused and re-renders the main view, and only then
does the handler return ErrQuit. With no changed files that render is a
string task, whose whole body is one hop to the UI thread — a hop that
is never served, because the handler's ErrQuit has meanwhile brought the
main loop down. The task can't finish, so the ViewBufferManager.Close
that follows waits for it until it times out. (With changed files it's a
command task instead, and every blocking point in one of those selects
on the stop channel, so Close gets through.)

A wait for the UI thread now ends when the loop does. That also covers
the command task's own hops, which are stopped only in between them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:03:26 +02:00
Stefan Haller 70427c8ff5 Add a test for waiting on the UI thread after the loop has exited
Nothing dequeues user events once MainLoop has returned, so a worker
blocked in OnUIThreadAndWait is blocked for good. The assertion records
that; the next commit makes the wait give up instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:03:26 +02:00
Stefan Haller 2f06724b80 Make RefreshHelper pay attention to the error returned from OnUIThreadAndWait
Right now the function always returns nil, but this will change later in
this branch, so handle errors properly. Without that, the first capture
that assigns env.git would not run, leave env.git nil, and subsequent
code would crash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:03:26 +02:00
Stefan Haller f9b790a1f9 Let OnUIThreadAndWait's error be about the wait, not about f
Every caller passes an f that unconditionally returns nil, so f's error
return has never carried anything: the value is dead weight, and it
occupies the one channel the wait itself needs to report that it couldn't
run f at all. Drop it, so that the error the wait returns can only ever
mean that.

Work that can fail hands its error back through a captured variable, the
way the background fetch already hands back four values, which keeps the
two outcomes distinguishable at a call site that has both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:04:53 +02:00
Stefan Haller ddceff6962
Bump mheap/github-action-required-labels from 5.5.2 to 5.6.0 (#5769)
Bumps
[mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels)
from 5.5.2 to 5.6.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mheap/github-action-required-labels/releases">mheap/github-action-required-labels's
releases</a>.</em></p>
<blockquote>
<h2>v5.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump GitHub client to v7 by <a
href="https://github.com/VincentLanglet"><code>@​VincentLanglet</code></a>
in <a
href="https://redirect.github.com/mheap/github-action-required-labels/pull/96">mheap/github-action-required-labels#96</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/VincentLanglet"><code>@​VincentLanglet</code></a>
made their first contribution in <a
href="https://redirect.github.com/mheap/github-action-required-labels/pull/96">mheap/github-action-required-labels#96</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/mheap/github-action-required-labels/compare/v5.5.2...v5.6.0">https://github.com/mheap/github-action-required-labels/compare/v5.5.2...v5.6.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="23e10fde7e"><code>23e10fd</code></a>
Automatic compilation</li>
<li><a
href="6e4081ebcb"><code>6e4081e</code></a>
Replace nock with undici in tests + bump GH client to v7 (<a
href="https://redirect.github.com/mheap/github-action-required-labels/issues/96">#96</a>)</li>
<li>See full diff in <a
href="0ac283b4e6...23e10fde7e">compare
view</a></li>
</ul>
</details>
<br />
2026-08-08 17:59:43 +02:00
dependabot[bot] f416a4ba6a
Bump mheap/github-action-required-labels from 5.5.2 to 5.6.0
Bumps [mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels) from 5.5.2 to 5.6.0.
- [Release notes](https://github.com/mheap/github-action-required-labels/releases)
- [Commits](0ac283b4e6...23e10fde7e)

---
updated-dependencies:
- dependency-name: mheap/github-action-required-labels
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 15:57:21 +00:00
Stefan Haller 2399cac0db
Bump github.com/kyokomi/emoji/v2 from 2.2.13 to 2.2.14 (#5813)
Bumps [github.com/kyokomi/emoji/v2](https://github.com/kyokomi/emoji)
from 2.2.13 to 2.2.14.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/kyokomi/emoji/releases">github.com/kyokomi/emoji/v2's
releases</a>.</em></p>
<blockquote>
<h2>v2.2.14</h2>
<h2>What's Changed</h2>
<h3>Emoji data</h3>
<ul>
<li>Regenerated <code>emoji_codemap.go</code> with Unicode Emoji 16.0 /
17.0 additions — 18 new emoji such as <code>:fingerprint:</code>,
<code>:orca:</code>, <code>:treasure_chest:</code>,
<code>:ballet_dancer:</code> (no removals)</li>
</ul>
<h3>Behavior change</h3>
<ul>
<li><code>NormalizeShortCode</code> now consistently returns the
lowercase alias when same-length aliases differ only in case. Affects 26
entries (e.g. <code>:Aries:</code> → <code></code>,
<code>:OK_hand:</code> → <code>👌</code>, <code>:ZZZ:</code> →
<code>💤</code>, <code>:T-Rex:</code> → <code>🦖</code>)</li>
</ul>
<h3>Maintenance</h3>
<ul>
<li>go.mod: <code>go 1.14</code> → <code>go 1.21</code> (cmd module:
<code>go 1.12</code> → <code>go 1.25</code>, goquery v1.5.1 →
v1.12.0)</li>
<li>CI: pin Go via <code>go-version-file</code>, golangci-lint-action v6
→ v8, verify cmd module</li>
<li>Added dependabot for github-actions and gomod</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/kyokomi/emoji/compare/v2.2.13...v2.2.14">https://github.com/kyokomi/emoji/compare/v2.2.13...v2.2.14</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a659fe56a6"><code>a659fe5</code></a>
Maintenance: update emoji data, modernize Go/CI, add dependabot (<a
href="https://redirect.github.com/kyokomi/emoji/issues/65">#65</a>)</li>
<li><a
href="eb10848906"><code>eb10848</code></a>
Bump GitHub workflow actions (<a
href="https://redirect.github.com/kyokomi/emoji/issues/64">#64</a>)</li>
<li>See full diff in <a
href="https://github.com/kyokomi/emoji/compare/v2.2.13...v2.2.14">compare
view</a></li>
</ul>
</details>
<br />
2026-08-08 17:55:18 +02:00
dependabot[bot] d6cf948dca
Bump github.com/kyokomi/emoji/v2 from 2.2.13 to 2.2.14
Bumps [github.com/kyokomi/emoji/v2](https://github.com/kyokomi/emoji) from 2.2.13 to 2.2.14.
- [Release notes](https://github.com/kyokomi/emoji/releases)
- [Commits](https://github.com/kyokomi/emoji/compare/v2.2.13...v2.2.14)

---
updated-dependencies:
- dependency-name: github.com/kyokomi/emoji/v2
  dependency-version: 2.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 15:52:34 +00:00
Stefan Haller 4b257646ad
Bump actions/setup-go from 6 to 7 (#5843)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to
7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-go/releases">actions/setup-go's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/763">actions/setup-go#763</a></li>
<li>chore(deps): bump <code>@​actions/cache</code> to 6.2.0 by <a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v7.0.0">https://github.com/actions/setup-go/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<h3>Dependency update</h3>
<ul>
<li>Upgrade actions dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-go/pull/744">actions/setup-go#744</a></li>
<li>Upgrade <code>@​types/node</code> and typescript-eslint dependencies
to resolve npm audit findings by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/755">actions/setup-go#755</a></li>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/758">actions/setup-go#758</a></li>
<li>Upgrade version to 6.5.0 in package.json and package-lock.json by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/762">actions/setup-go#762</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/744">actions/setup-go#744</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/758">actions/setup-go#758</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v6.5.0">https://github.com/actions/setup-go/compare/v6...v6.5.0</a></p>
<h2>v6.4.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Add go-download-base-url input for custom Go distributions by <a
href="https://github.com/gdams"><code>@​gdams</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/721">actions/setup-go#721</a></li>
</ul>
<h3>Dependency update</h3>
<ul>
<li>Upgrade minimatch from 3.1.2 to 3.1.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/727">actions/setup-go#727</a></li>
</ul>
<h3>Documentation update</h3>
<ul>
<li>Rearrange README.md, add advanced-usage.md by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/724">actions/setup-go#724</a></li>
<li>Fix Microsoft build of Go link by <a
href="https://github.com/gdams"><code>@​gdams</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/734">actions/setup-go#734</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gdams"><code>@​gdams</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/721">actions/setup-go#721</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v6.4.0">https://github.com/actions/setup-go/compare/v6...v6.4.0</a></p>
<h2>v6.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default Go module caching to use go.mod by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/705">actions/setup-go#705</a></li>
<li>Fix golang download url to go.dev by <a
href="https://github.com/178inaba"><code>@​178inaba</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/469">actions/setup-go#469</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v6.3.0">https://github.com/actions/setup-go/compare/v6...v6.3.0</a></p>
<h2>v6.2.0</h2>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b7ad1dad31"><code>b7ad1da</code></a>
chore(deps): bump <code>@​actions/cache</code> to 6.2.0 (<a
href="https://redirect.github.com/actions/setup-go/issues/771">#771</a>)</li>
<li><a
href="0778a10ce4"><code>0778a10</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-go/issues/763">#763</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/setup-go/compare/v6...v7">compare
view</a></li>
</ul>
</details>
<br />
2026-08-08 17:50:54 +02:00
dependabot[bot] d3a2c87293
Bump actions/setup-go from 6 to 7
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-08 15:47:46 +00:00
Stefan Haller f162dc5aec
Honor the conflict-marker-size gitattribute (#5902)
If the `conflict-marker-size` git attribute is used to set the marker
size to a non-default value (!= 7), lazygit's handling of conflicted
files was totally broken. Stopping at a commit with conflicts in a
rebase would show the `UU` files for a moment, and then, a few seconds
later, would stage all conflicted files and offer to continue the rebase
(with the conflicts baked into the resulting commits if you confirmed).
Even if you cancelled the continue prompt, it wasn't possible to use
git's conflict panel to resolve the conflicts; it would only show the
regular diff for those files, not its conflicts editor.

Fix this by querying the `conflict-marker-size` git attribute for all
conflicting files and use that to match the conflict markers.

Fixes #4367.
2026-08-08 12:58:14 +02:00
Stefan Haller d0078bf05c Recognize conflict markers that have no label
Git only writes the space after a marker when there is a label to write
after it, and the label can be empty: `git checkout -m` with the diff3
conflict style, for instance, has no name for the common ancestor, so it
writes a bare "|||||||" line.
2026-08-08 12:43:42 +02:00
Stefan Haller 5481436d8c Honor the conflict-marker-size gitattribute
Ask git for the attribute of every conflicted file whenever we load the
file status, so that we recognize the markers it actually wrote. Files
that are set up this way are precisely the ones whose regular content
tends to contain marker-looking lines, so matching a run of at least
seven characters instead is not an option: we'd take the file's own
content for markers and then never consider its conflicts resolved.

One `git check-attr` call covers all conflicted files at once; asking per
file would take seconds when hundreds of files are conflicted, and it
would hurt worst on Windows, where spawning a process is expensive.
Because the lookup rides along with the file status, it costs nothing
when there are no conflicts, and editing .gitattributes during a merge
takes effect on the next refresh.
2026-08-08 12:43:42 +02:00
Stefan Haller c3450f9406 Add tests demonstrating that we ignore the conflict-marker-size gitattribute
When a file's conflict markers aren't seven characters long we don't
recognize them at all. Two things go wrong: we consider the file's
conflicts resolved, so we stage it and offer to continue the merge a
moment after stopping at it; and pressing enter on it shows its diff
instead of the merge conflicts view, leaving no way to resolve it in
lazygit.
2026-08-08 12:43:42 +02:00
Stefan Haller bc9fafff02 Make the conflict marker size a parameter of our marker matching
Git doesn't always write conflict markers of seven characters: the
conflict-marker-size gitattribute overrides that per file, and it is set
for good reasons — for file types whose regular content tends to contain
marker-looking lines, such as documentation about merging, or test
scripts. We hard-code seven characters everywhere we look for markers,
so none of that works.

Prepare for honoring the attribute by threading the marker size through
everything that recognizes a marker, carried on the file model. Nothing
fills it in yet, so we still use git's default size of seven everywhere,
and matching is unchanged: a marker consists of exactly that many marker
characters, and all but the "=======" one are followed by a space and a
label.
2026-08-08 12:43:42 +02:00
Stefan Haller 5dec89abfe
Update the UI after stash operations in a single frame (#5905)
This fixes a regression in 0.64.0: before that version, creating or
popping a stash would happen synchronously on the UI thread (including
the refresh), blocking the UI until everything changed, including the
panel focus. Blocking the UI was not nice of course, but at least the UI
update was clean. With 0.64.0 this changed to a background refresh, so
that the update to the two panels and the focus change all happened out
of sync, which looks rather ugly. Fix this by using Refresh's mechanism
to batch UI updates, and switch the panel focus in the Refresh's Then so
that it updates at the same time.

While we're at it, use a waiting status spinner for these operations;
they are usually fast when only few files are involved, but when
stashing a large number of files in a larger repo it can be noticeable,
and it looks ugly if the confirmation prompt stays on the screen while
it is running.
2026-08-08 12:43:29 +02:00
Stefan Haller 4c39b0b903 Run the stash operations with a waiting status
Creating and applying a stash both touch every changed file, so in a
large repo they can take long enough to be noticeable — and running them
on the UI thread meant the confirmation popup stayed on screen, frozen,
for the whole operation. Run them on a worker instead, with a spinner,
and keep blocking input for their duration so that the type-ahead
guarantee the refresh used to provide still holds.

Dropping stays on the UI thread: it only rewrites the stash reflog, so
it's fast no matter how big the stashes are.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:26:18 +02:00
Stefan Haller ed22322ec8 Collapse the stash range selection from the refresh's Then
Collapsing the range before kicking off the refresh paints the new
selection against the list as it was before the drop, so for a frame the
entries that were just dropped are still on screen (and, with
gui.shrinkSidePanelsToContent, the panel is still at its old size).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:26:18 +02:00
Stefan Haller 6c567d1eb6 Switch to the files panel from the post-stash refresh's Then
Pushing the files context right after kicking off the refresh moves the
focus (and, with gui.shrinkSidePanelsToContent, resizes the panels) a
frame before the refreshed stash and files lists arrive. Doing it from
Then puts it in the same frame as the data it belongs to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:26:18 +02:00
Stefan Haller 544f3b834b Apply the panel updates after stash operations in a single frame
Stashing and popping change both the stash list and the files list.
With each scope updating the UI as soon as its own refresh is done, the
two panels visibly change at different times; with
gui.shrinkSidePanelsToContent that also means their sizes change at
different times than their contents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:26:18 +02:00
Stefan Haller da7ca77c9d AGENTS.md additions 2026-08-08 12:26:18 +02:00
Stefan Haller bf5829af3f
Fix several problems with repos whose git dir lives outside the working tree (#5910)
Lazygit assumes a repo can be found again from its working directory: it
chdirs there and lets git rediscover the git dir from `<worktree>/.git`.
That holds for an ordinary repo and breaks for every setup where the git
dir lives somewhere else, which is where these bugs come from. Opening
such a repo worked at all only when `--git-dir` happened to leave
`GIT_DIR` in the environment for every command to inherit — which is
also why entering a submodule, which has to clear it, broke the way back
out.

Three reported problems:

- **`core.worktree` (#5895).** A repo whose work tree is elsewhere
panicked on startup with `fatal: not a git repository`: we chdir'd into
a work tree with no `.git` in it and every command after that was lost.
We now work out at startup whether git can find the repo from its work
tree, and when it can't we put `GIT_DIR`/`GIT_WORK_TREE` on every
command the repo's command builder produces — as well as in the process
environment, for subprocesses that don't come through the builder.
Nothing is set for the repos git can find on its own, which is nearly
all of them.

- **Escaping a submodule of a dotfile repo (#1118).** The repo-path
stack we push the superproject onto only held its path, and for a repo
opened with `--git-dir`/`--work-tree` the path leads nowhere. Escaping
failed with `not a git repository`, or, if some unrelated repo happened
to lie above the work tree, quietly switched to that one instead. The
stack now carries the environment as well, taken from the repo paths
rather than from the process env, so it also covers a repo whose
location lazygit worked out itself.

- **Opening a directory that holds a bare repo (#5469, #5681).** `git
rev-parse --show-toplevel` is fatal when there's no work tree, so we
never got an answer at all for a bare repo: `IsBareRepo()` could never
come out true, and lazygit either died with a stack trace or decided we
weren't in a repository. We now ask again without `--show-toplevel` when
the first query fails, and the existing "open most recent repo?" prompt
does its job.

Some related things that turned up on the way:

- **A submodule no longer looks like a linked worktree.** `git worktree
list` reports the main worktree as the common git dir with a trailing
`/.git` removed, which is not the working tree when the git dir doesn't
live inside it. Comparing that against the working tree path matched
nothing, so inside a submodule the status bar claimed we were in a
linked worktree named after the submodule, the worktrees panel listed it
as not current, and its branch got a "checked out elsewhere" marker.
Worktrees are now identified by their git dir, which names them
unambiguously.

- **Commands aimed at another repo no longer resolve against ours.**
With `GIT_DIR` set, `git -C mysub log -1` reports the *superproject's*
commit, silently. So opening lazygit with `--git-dir`/`--work-tree`
quietly broke resolving submodule conflicts, stashing and resetting a
submodule, and detaching another worktree.

- **Starting lazygit in a repo's `.git` dir opens the repo.** It used to
tell you that you were in a bare repo, which you weren't — the work tree
was one directory up. git's own convention is that a git dir called
`.git` belongs to the directory holding it, so we look there. (A linked
worktree's or a submodule's git dir isn't called `.git`, and nothing we
look at says where their work tree is, so those still get the prompt.)

- **`RepoPath()`** is documented to be the work tree when we're in the
main worktree, but was derived from the git dir's location, which is
only the same thing when the git dir is inside the work tree. This fixes
the repo name shown in the status panel for split setups.

Fixes #1118
Fixes #5469
Fixes #5681
Fixes #5736
Fixes #5895
2026-08-08 12:20:44 +02:00
Stefan Haller f141fcc570 Open the repo when lazygit is started in its .git dir
Running lazygit in a .git dir got you told you were in a bare repo,
which you weren't: the worktree was sitting right there, one directory
up. git's own convention is that a git dir called .git belongs to the
directory holding it — that's how `git worktree list` names the main
worktree — so ask that directory, and if it is a worktree, open the repo
we were really being asked about.

The git dirs that aren't called .git keep the answer they had. A linked
worktree's and a submodule's do have a worktree, but nothing we look at
says where, so we would be guessing; a bare repo's has none to find.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 06b421ad0c Remember how to get back to a repo we entered a submodule from
Entering a submodule clears GIT_DIR and GIT_WORK_TREE, as it must: they
say where the superproject is. But the stack we push the superproject
onto so that escape brings us back only held its path, and for a repo
opened with --git-dir/--work-tree the path leads nowhere — git can't
find a repo there. Escaping out of a submodule of a dotfile repo failed
with "not a git repository", or, if some unrelated repo happened to lie
above the work tree, quietly switched to that one instead.

Push the environment onto the stack along with the path, taken from the
repo paths rather than from the process env, so that it also covers a
repo we worked the location out for ourselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 9b1078a2ca Make StringStack generic
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller d19af37ee7 Tell git where the repo is when it can't find it itself
git finds a repo by looking for a .git in the directory a command runs
in. Lazygit runs its commands in the work tree, so that normally works —
but not when the git dir lives somewhere else entirely, which is what
core.worktree and --work-tree are for. Lazygit chdir'd into such a work
tree and then ran commands that couldn't see any repo from there, so
opening a repo with core.worktree set panicked on startup. It only
worked with --git-dir because that leaves GIT_DIR in the environment for
every command to inherit.

Work out at startup whether git can find the repo from its work tree,
and when it can't, put GIT_DIR and GIT_WORK_TREE on every command the
repo's builder produces. As with the working directory the builder pins
(527124d0e0), these also go into the process env — subprocesses don't
come through the builder — but the commands don't read them from there,
because the process env belongs to whichever repo we have switched to
since.

Working out whether git can find the repo means asking git, rather than
reading the .git file, whose contents can spell the same directory
differently than git does. The extra query is skipped for a repo whose
git dir is simply its .git directory, which is nearly all of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 34d41b5d51 Don't let our repo answer for a different one
GIT_DIR and GIT_WORK_TREE tell git where our repo is, and every command
we run inherits them — including the ones we point at a submodule or
another worktree. git resolves those against our repo instead, and says
nothing about it: with GIT_DIR set, `git -C mysub log -1` reports the
superproject's commit. So opening lazygit with --git-dir/--work-tree
quietly broke resolving submodule conflicts, stashing and resetting a
submodule, and detaching another worktree; the worktree list came back
claiming every worktree shared our git dir.

Drop the two variables from the commands that address another repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 616d75a1fa Run Reset in the parent module the way the other commands do
Reset told git to change directory with -C while runInParentModule does
it by setting the command's working directory, but they were computing
the same directory for the same reason. Use the helper, so that there is
one place that knows what running in a nested submodule's parent means.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 3d80e466ce Say why runInParentModule can name a relative directory
Its working directory resolves against the process rather than against
the repo the command builder pins commands to, which is only safe
because nothing but foreground commands come through here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 0ce248d1bf Recognize a repo that has no work tree instead of bailing out
git makes `rev-parse --show-toplevel` fatal when there's no work tree,
so asking for it together with everything else meant we never got an
answer at all for a bare repo: GetRepoPaths returned an error, nobody
ever saw IsBareRepo() == true, and lazygit either died with a stack
trace or decided we weren't in a repository. That's what you got for
opening it in a directory holding a bare repo and a .git file pointing
at it, which is a normal way to keep a repo and its worktrees together.

Ask again without --show-toplevel when the first query fails: the other
queries work fine without a work tree, so if they now succeed we know
we're in a bare repo, and the existing prompt offering to open a recent
repo does its job. If they fail too we're not in a repo at all, and the
first error already says so.

--is-bare-repository is gone from the query: a work tree implies
core.bare is false, so it could only ever come back false there, and
what matters to us is whether there is a work tree to show, which is
what we now go by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller e10a2f6a27 Stop promising bare repo support
"does not yet support" reads as a promise that it will, but it's quite
likely that it never will.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller ca6c0500e6 Don't clear gui.git when we fail to open a repo
onNewRepo also runs when switching repos, and a failure there leaves us
in the repo we came from — with a nil GitCommand, which nothing else is
prepared for. Only assign once we have one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller e17ed2484c Use the work tree as the repo path when it is the main worktree
RepoPath() is meant to be the same as WorktreePath() when we're in the
main worktree, but we derived it from the git dir's location instead.
That is only the same thing when the git dir lives inside the work tree.
With core.worktree, --work-tree, or a .git file pointing at a repo dir
that isn't called .git, it lands on a directory that isn't a worktree at
all, and the repo name we show follows it there.

A worktree that has the repo's common git dir to itself is the main
worktree, so use its path. That subsumes the submodule case, whose git
dir lives under the superproject's .git/modules but is still the
submodule's own common dir; --show-superproject-working-tree is now only
needed for a linked worktree of a submodule.

The existing bare repo test asserted a git output that can't occur (a
work tree and --is-bare-repository=true at once), but the rest of it is
the shape of a repo opened with --git-dir/--work-tree, where the new
repo path is the correct one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 180039e78c Add a repo paths test for a repo with a separate work tree
When the work tree lives somewhere else entirely — set up with
core.worktree or --work-tree — we're still in the main worktree, so
RepoPath() should be the work tree, as its own doc comment says. Instead
we derive it from the git dir's location, which lands somewhere that
isn't a worktree at all, and the repo name follows it.

The ACTUAL lines are indented as they will be once the EXPECTED ones
replace them, rather than as gofumpt wants them while the comment
markers are still splitting the struct's alignment. That leaves this one
file not gofumpt-clean until the next commit, in exchange for a diff
there that shows only the lines that actually change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller d2d5bdc2bc Identify the current and main worktree by git dir, not by path
`git worktree list` reports the main worktree as the common git dir with
a trailing "/.git" removed, which equals the working tree only when the
git dir sits inside it. In a submodule, a bare repo, or a repo using
core.worktree it doesn't, so comparing the reported path against the
working tree path matches nothing: no worktree is recognized as current
or as main. Most visibly, inside a submodule lazygit claimed we were in
a linked worktree named after the submodule, and offered to remove that
"worktree".

Comparing git dirs identifies a worktree unambiguously, so use that.
A worktree whose directory is gone has no git dir to compare, and there
we still have nothing better than its path.

The submodule tests were asserting the linked-worktree suffix in the
status view; it is gone now, and the repo name still says which
submodule we're in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller e1b8ef048a Add a worktree loader test for being in a submodule
A submodule's git dir doesn't live inside its working tree, and `git
worktree list` reports it by its git dir. Lazygit compares that against
the working tree path, so it recognizes neither the current nor the main
worktree, and the UI ends up claiming we're in a linked worktree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller 7cbd93f945 Give the worktree loader tests their repos' git dirs
The scenarios describe their repo by its paths but leave the git dirs
empty, which no repo has. Unused for now; the loader is about to want
them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00
Stefan Haller d1014aecf3
Simplify the run_integration_tests.sh script (#5908)
Our minimum required git version is 2.32.0, which is the first version
that does support the GIT_CONFIG_GLOBAL env var, so we don't need the
~/.gitconfig dance any more.
2026-08-08 09:52:20 +02:00
Stefan Haller e1391298aa Simplify the run_integration_tests.sh script
Our minimum required git version is 2.32.0, which is the first version
that does support the GIT_CONFIG_GLOBAL env var, so we don't need the
~/.gitconfig dance any more.
2026-08-08 09:31:58 +02:00
Stefan Haller 813de837ef
update Nix flake dependencies (#5894)
### PR Description

Refresh the Nix flake inputs - see #5882.

Direct input revisions:

- `flake-parts`: `758cf7296b` → `427bf4bd94`
- `nixpkgs`: `c9b6fb7985` → `e72e4f2994`
- `treefmt-nix`: `5eda4ee812` → `d1187f8bc7`
- `systems`: unchanged at `da67096a3b`
- `flake-compat`: unchanged at `ff81ac966b`; its immutable URL metadata
  was normalized by the lock refresh

The refresh also updates the transitive `nixpkgs-lib` and
`treefmt-nix/nixpkgs` inputs.

The newer nixpkgs revision deprecates `pkgs.nixfmt-rfc-style` because it
now aliases `pkgs.nixfmt`. Uses the current attribute directly,
preserving
formatter behavior and removing the two evaluation warnings.

### Testing

- `nix flake check`
- `nix develop --ignore-environment -c just format` with a writable
  `GOCACHE`
- `nix develop --ignore-environment -c just build` with a writable
  `GOCACHE`
- `nix develop --ignore-environment --keep HOME -c just unit-test` with
  a writable `GOCACHE`
- `nix develop --ignore-environment --keep HOME -c just lint` with
  managed proxy variables and task-local caches
- `nix-shell --run 'just --version'`
- `git diff --check`

### Please check if the PR fulfills these requirements

- [x] Cheatsheets are up-to-date — no integration-test or keybinding
changes
- [x] Code has been formatted
- [x] Tests have been added/updated — no test changes needed; unit suite
passes
- [x] Text is internationalised — no user-facing strings changed
- [x] UserConfig hot reload is unaffected — no UserConfig changes
- [x] Docs have been updated if necessary — no documentation changes
needed
- [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view',
and make sure the title
is suitable to be included as a bullet point in release notes (i.e.
phrased from a user's point
of view).
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2026-08-06 16:51:55 +02:00
Stefan Haller 71ff5fd827 Update the versions pinned for the Nix build
flake.lock pins the exact revision of everything the Nix build pulls in
(the package set it takes git, Go and the formatters from, plus a few
helper libraries), much like go.sum does for Go modules. Nothing
refreshes it automatically and no CI job exercises it, so the pins had
quietly drifted the better part of a year behind.

The flake-compat entry also grows a "?rev=...&revCount=..." suffix on
its download URL. That is not a version change: the server hosting the
archive advertises those two values as part of the canonical URL now,
and they only restate the rev and revCount that the same entry already
lists as separate fields. The archive fetched is byte-for-byte identical
either way.
2026-08-06 16:49:23 +02:00
Stefan Haller a9bb960e5c Use nixfmt's current package name in the flake
The nixpkgs package set renamed the Nix code formatter: what used to be
called nixfmt-rfc-style is now simply nixfmt, and the old name lives on
only as an alias. Both names resolve to the identical program, so this
doesn't change how anything gets formatted.

The rename comes first because the dependency update in the next commit
makes that alias start printing a deprecation warning, so doing it in
this order means no commit in the history evaluates with one.
2026-08-06 16:49:23 +02:00
Stefan Haller 900c3e3c45
Fix race in "Stash staged changes" on git versions before 2.32.0 (#5903)
PipeCommands ran every command in its own goroutine, each doing
Start/read-stderr/Wait, with nothing ordering one goroutine's Start
against another's Wait. That ordering matters: StdoutPipe registers the
parent's read end in cmd.parentIOPipes, and Cmd.Wait closes those
descriptors when it returns. The next command's Stdin is that very
*os.File, and exec passes a user-supplied *os.File through untouched, so
Start hands the child whatever the fd happens to be at that moment. If
the producer finished and got reaped before the consumer's goroutine
reached Start, that fd was already closed, File.Fd() returned -1, and
the child was started with fd 0 closed -- reading nothing at all.

The only caller is the pre-2.35 fallback in SaveStagedChanges, which
pipes `git stash show -p` into `git apply -R`. Losing that race left git
apply with an empty patch, so it failed with "unrecognized input", the
following `git stash drop` never ran, and the user was left with a stray
stash entry. This turned up as a flaky stash/stash_staged on the git
2.32.0 CI job; the newer-git jobs take the `git stash push --staged`
path and never reach this code.

Starting every command up front removes the race, and collecting stderr
into buffers lets exec's own copying goroutines do the work. That also
fixes two lesser problems in the same function: finalErrors was appended
to from several goroutines without synchronization, and a failed Start
was only logged, so a pipeline that never ran reported success.
2026-08-06 16:14:08 +02:00
Stefan Haller d2a1a4f2a2 Start all commands of a pipeline before waiting for any of them
PipeCommands ran every command in its own goroutine, each doing
Start/read-stderr/Wait, with nothing ordering one goroutine's Start
against another's Wait. That ordering matters: StdoutPipe registers the
parent's read end in cmd.parentIOPipes, and Cmd.Wait closes those
descriptors when it returns. The next command's Stdin is that very
*os.File, and exec passes a user-supplied *os.File through untouched, so
Start hands the child whatever the fd happens to be at that moment. If
the producer finished and got reaped before the consumer's goroutine
reached Start, that fd was already closed, File.Fd() returned -1, and
the child was started with fd 0 closed -- reading nothing at all.

The only caller is the pre-2.35 fallback in SaveStagedChanges, which
pipes `git stash show -p` into `git apply -R`. Losing that race left
git apply with an empty patch, so it failed with "unrecognized input",
the following `git stash drop` never ran, and the user was left with a
stray stash entry. This turned up as a flaky stash/stash_staged on the
git 2.32.0 CI job; the newer-git jobs take the `git stash push --staged`
path and never reach this code.

Starting every command up front removes the race, and collecting stderr
into buffers lets exec's own copying goroutines do the work. That also
fixes two lesser problems in the same function: finalErrors was appended
to from several goroutines without synchronization, and a failed Start
was only logged, so a pipeline that never ran reported success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:34:47 +02:00
Stefan Haller bd76666d97
Fix transitions of entering and exiting filtering mode (e.g. by path or author) (#5897)
Entering or leaving filtering mode switched the screen mode and the
focused panel immediately, then reloaded the commit list in the
background. The result was an unfiltered list presented in the layout
that says "you are filtering", with nothing to say that anything was
still happening — and in a big repo that state can last seconds.

This is a regression in 0.64.0 (more specifically, from #5790): before
that we stopped blocking the UI thread on refreshes, the reload happened
before any of it, so the two always agreed; the price was a frozen UI
for the duration.

Do neither: reload on a worker, so the UI stays live, and hold back
everything the user can see of the change until the new lists are ready,
so they still land together in one frame. A waiting status says what is
going on in the meantime, and blocking input means the keys pressed
while it runs arrive after the change rather than acting on a list that
is about to be replaced.
2026-08-05 17:49:00 +02:00
Stefan Haller 1b901c7187 Scroll the selection into view after a filtering mode change
The commit list a filtering mode change leaves behind has nothing to do
with the one that was showing, so the scroll position it inherits says
nothing about where the selection ended up, and the selection can land
anywhere off screen. PostRefreshUpdate only moves the cursor within the
existing scroll position, so ask for the scroll separately, the way the
commits refresh does when it moves the selection itself.

Exiting filtering mode looked like it worked, but only by accident: the
commits refresh recognizes the commit that was selected before it ran,
selects it again at its new index, and scrolls because the index moved.
That does nothing for the case where the commit is gone from the list, or
for entering filtering mode, where we select the first commit ourselves.
2026-08-05 17:29:20 +02:00
Stefan Haller 7e6d5ff7c1 Don't show an unfiltered list as if it were filtered
Entering or leaving filtering mode switched the screen mode and the
focused panel immediately, then reloaded the commit list in the
background. The result was an unfiltered list presented in the layout
that says "you are filtering", with nothing to say that anything was
still happening — and in a big repo that state can last seconds.

Before we stopped blocking the UI thread on refreshes, the reload
happened before any of it, so the two always agreed; the price was a
frozen UI for the duration.

Do neither: reload on a worker, so the UI stays live, and hold back
everything the user can see of the change until the new lists are ready,
so they still land together in one frame. A waiting status says what is
going on in the meantime, and blocking input means the keys pressed while
it runs arrive after the change rather than acting on a list that is
about to be replaced.
2026-08-05 17:29:20 +02:00
Stefan Haller b30c734513 Handle entering and leaving filtering mode in one place
Setting a filter and clearing it are the same transition in opposite
directions: mutate the mode, bring the screen mode in line with it,
reload the views that depend on the filter, and put the selection
somewhere sensible in the reloaded commit list. They were implemented
twice, once in the filtering menu and once in ModeHelper, which is how
the two came to repaint the commit list in different ways.

Derive the screen mode and the panel switch from whether a filter is
active after the change, so both directions fall out of the same code,
and give ModeHelper the entry points for both. The filtering menu is
left with nothing but the menu.
2026-08-05 17:29:20 +02:00
Stefan Haller e54cb4bf42 Decouple hiding the working tree state from blocking input
Blocking keyboard input and hiding the working tree state mode are two
separate concerns; they were fused into one helper because every caller
so far wanted both. A caller that blocks input for something other than a
rebase would then hide the "Rebasing" indicator for the duration of its
operation, which has nothing to do with it.

Make it an explicit option instead, so blocking input on its own doesn't
imply anything about the modes on display.
2026-08-05 17:29:20 +02:00
Stefan Haller f4968f6839 Rename suppressRebasingMode to suppressWorkingTreeStateMode
The mode it suppresses is active for any working tree state, not just a
rebase: merging, cherry-picking and reverting show through the same
indicator. Name it after what it hides.
2026-08-05 17:29:20 +02:00
Stefan Haller f8b7bab1ab Decide the commit graph from the loaded list, not the filtering mode
Whether a graph can be drawn was read from the filtering mode, while the
graph itself is drawn over the commit list in the model. Those two only
agree once the list has been reloaded for the new mode, and a filtering
mode change reloads the list in the background, so in between we can be
asked to draw a graph over a list the graph makes no sense for.

That is not just cosmetic. Commits in a filtered list are almost never
each other's parents, so no pipe ever terminates: the pipe set grows by
one per row and every continuing pipe rescans it, which is cubic in the
length of the list. Escaping out of filtering mode with a filtered list
of 13000 commits — as you get once the 300 commit limit has been lifted,
which happens for good as soon as the selection passes COMMIT_THRESHOLD
— wedges the UI thread for around twenty minutes.

Record whether the list was loaded with a filter, right where the list
itself is stored, and decide from that. The graph now also stays up while
the pre-change list is still on display, rather than vanishing a moment
before the list it belongs to.
2026-08-05 17:29:20 +02:00
Stefan Haller 8996bd68b9
Don't let integration tests race a background git repack (#5898)
This fixes spurious test failures caused by git's auto maintenance
running concurrently with a test's fixture setup; see the first commit's
message for details.

In addition, we change the test harness so that such a failure in a
setup method doesn't take down the whole test binary.
2026-08-05 17:28:56 +02:00
Stefan Haller 34da956f5d Don't let a broken fixture take down the whole test binary
A failing setup step called Shell.fail, which panicked. Tests run as
parallel subtests, so that panic aborted the entire test binary: one bad
fixture cost us the results of all ~500 tests, and the failure was
reported as a stack trace rather than against the test that caused it.

Keep panicking to skip the remaining setup steps -- they would only
produce follow-on failures -- but recover in createFixture and return the
message as that test's error. All three clients already propagate an
error from a test, so they report it the way they report any other
failure.
2026-08-05 17:02:06 +02:00
Stefan Haller 4ec91a0bf5 Don't let integration tests race a background git repack
Every `git commit` forks `git maintenance run --auto --quiet --detach`,
and git 2.54 changed what that runs from the `gc` task to the
"geometric" strategy. The geometric repack's auto condition passes its
threshold of 100 to too_many_loose_objects(), which estimates the loose
object count from the objects/17 fanout directory times 256, so the real
trigger is two objects in that one directory -- where the old gc task
needed 27. Fixture repos reach two easily: every CreateNCommits(n>=6)
repo already stores the blob for file06.txt there, so a single commit
object hashing into 17 (about 4% of fixtures with 10 commits, 15% with
40) tips it over, and from then on every commit in that repo forks a
detached `git repack -d`, which prunes loose objects while the next
fixture command -- or lazygit under test -- is still working in the same
repo.

That is where the CI panics during fixture setup come from:

    panic: error running command: [git commit -m commit-10]
        error: invalid object 100644 50d5612... for 'file09.txt'
        error: Error building trees

The reported hashes are exactly the fixture blobs, so `git add` staged
them correctly; they were unlinked underneath the commit. Only the
"git latest" jobs saw this, since the pinned 2.32/2.38/2.44 jobs predate
the strategy change.

git's own test suite guards against the same thing by exporting
GIT_TEST_MAINT_AUTO_DETACH=false ("Ensure that tests cannot race with
background maintenance by default"). Turning maintenance off outright is
stronger: no test repo needs it, and it also spares us a forked git
process per commit. maintenance.auto has been honored since git 2.29, so
it covers every version in the CI matrix.

Measured on a 40-commit fixture: a background repack fired in 4 of 25
runs before, 0 of 25 after.
2026-08-05 16:58:37 +02:00
Stefan Haller aee0e40ec1
Update docs and schema for release (#5892) 2026-08-04 09:22:45 +02:00
Stefan Haller a1a542479c Update docs and schema for release 2026-08-04 09:19:40 +02:00
Stefan Haller fc0a3a4e2a
Update translations from Crowdin (#5891) 2026-08-04 09:18:49 +02:00
Stefan Haller 1f231c9262 Update translations from Crowdin 2026-08-04 09:03:58 +02:00
Stefan Haller cca82cfe48
Make justfile commands available in the Nix development shell (#5890)
### Description

Add `just` to the default Nix development shell so contributors can use
the repository's `justfile` without installing `just` separately. Keep
GNU Make available as the existing fallback.

no `flake.lock` update required. `just` comes from the already-pinned
`nixpkgs` input, and no flake input changes.

Closes #5881.

### Testing

- `nix-instantiate --parse flake.nix`
- `nix flake check`
- `nix develop --ignore-environment -c just --version`
- `nix develop --ignore-environment -c go version`
- `nix develop --ignore-environment -c just format`
- `nix develop --ignore-environment -c just build`
- `nix develop --ignore-environment -c just lint`
- `nix develop --ignore-environment -c just unit-test`
- `nix-shell --run 'just --version'`

### Please check if the PR fulfills these requirements

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide) — no test-code change needed; validated
with the commands above
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
— no user-facing strings changed
* [x] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
— no UserConfig entry added
* [x] Docs have been updated if necessary — no documentation change
needed
* [x] You've read through your own file changes for silly mistakes etc
2026-08-04 07:50:12 +02:00
Tyce Herrman 1edd432d2f Make justfile commands available in the Nix development shell 2026-08-04 07:10:33 +02:00
Stefan Haller ccdfa8f4e3
Fix orphaned processes on Windows when quickly navigating between commits (#5885)
Fixes #5879: with an external diff command configured, quickly
navigating between commits on Windows accumulates orphaned
`git.exe`/`difft.exe`/`conhost.exe` processes that keep computing their
diffs in the background and persist after lazygit exits.

Stopping a pty task on Windows relied on `ClosePseudoConsole`, whose
`CTRL_CLOSE_EVENT` only reaches clients *attached to the pseudoconsole
at that moment*. Attachment happens asynchronously during child startup,
so a task stopped within the first few milliseconds of its life — which
is exactly what rapid navigation produces — misses the event entirely
and survives, together with its whole process tree (git for Windows
spawns through a two-level git.exe wrapper, so a single task has several
attach windows).

The fix puts the child into a job object before it executes its first
instruction (created suspended → assigned → resumed), so every
descendant is in the job from the start. The pty teardown still closes
the pseudoconsole first and gives clients that received the close event
a moment (500ms) to exit through their own handlers — git's cleans up
lock files — and then terminates whatever is left in the job.
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` doubles as a safety net: if lazygit
exits without running the teardown, the OS closes the job handle and
reaps the tree.

Validated with a harness that mimics lazygit's stop path with randomized
0–120ms stop delays: before the fix, 3 of 30 process trees survived as
permanent orphans per run; after it, three runs of 30 all came back with
zero orphans, with the job kill catching exactly the children that
missed the close event (6 of 90) while the rest still exited gracefully
through the event as before.
2026-08-04 07:06:26 +02:00
Stefan Haller d251faddec Reap pty process trees synchronously when quitting on Windows (#5879)
The pty teardown in Close runs on a background goroutine that doesn't
get to finish when lazygit is quitting: the process exits milliseconds
after the view buffer managers are closed. The job objects still cover
the clients -- KILL_ON_JOB_CLOSE reaps them when the process's handles
are rundown at exit -- but nothing reaps the conhost, so on Windows
builds whose conhost fails to run down on its own, quitting leaks one
conhost per live pty.

This is not a rare timing window: a diff longer than what has been
read keeps its git process (and thus its pty and conhost) running for
the entire time it is displayed, so that scrolling can read more.
Quitting while looking at a long diff is therefore the common case,
and with an external differ configured it leaks a conhost on affected
builds on almost every quit.

Fix this by having the gui's shutdown path wait synchronously for the
in-flight teardowns after closing the view buffer managers. A quit
signal makes the teardowns skip the conhost rundown wait -- the
conhost serves nothing once its clients are dead, and the exit must
not stall for its sake -- so the wait normally completes in
milliseconds, keeping quit as fast as before; a 2-second cap protects
the exit path even if a teardown wedges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 07:03:27 +02:00
Stefan Haller b4dee2af0e Reap the pty's conhost on Windows builds where it fails to run down (#5879)
A pseudoconsole's conhost.exe is spawned by CreatePseudoConsole as a
child of lazygit, so it is not part of the job object that the pty
teardown kills. That is normally fine: a healthy conhost runs itself
down once the reference handle is closed and its clients are gone. But
conhost builds before the ConPTY overhaul that shipped with Windows 11
24H2 (confirmed on 23H2, build 22631) fail to complete the rundown
when a client attached after the close event was delivered and was
then killed -- the fate of exactly the clients the job kill exists for
-- and such a conhost lingers forever with no clients, at a rate of
about one per five fast commit navigations. These builds remain
widespread: all of Windows 10 (whose ESU tail runs into 2028, and
whose hardware often cannot run Windows 11 at all) plus pre-24H2
Windows 11 fleets.

Since Windows offers no way to obtain the conhost's pid or handle from
the HPCON, identify it by diffing lazygit's direct conhost children
around the CreatePseudoConsole call, serialized by a mutex so that two
concurrently starting ptys can't confuse each other's diff, and open a
handle immediately so that pid reuse is harmless. The teardown then
gives conhost a second to exit on its own before terminating it; on
healthy builds the wait succeeds and the reap never fires. If the
conhost can't be identified unambiguously, we simply don't reap, which
is no worse than before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 07:03:27 +02:00
Stefan Haller 6048fee49d Kill the pty child's process tree on Windows to avoid orphans (#5879)
Stopping a pty task on Windows relies on ClosePseudoConsole, which
delivers CTRL_CLOSE_EVENT to the console's attached clients. But only
to those attached at that moment: when the user flicks quickly through
commits, a task is often stopped within the first few milliseconds of
its life, before the child has attached to the pseudoconsole. Such a
child misses the event and survives, running the entire diff to
completion in the background (spawning one external differ per changed
file) and keeping its conhost.exe alive; rapid navigation accumulates
these git/difft/conhost trees, and they outlive lazygit. Grandchildren
are affected too: git for Windows runs commands through a two-level
git.exe wrapper, so a single task has several attach windows, and a
grandchild spawned while the console is going down is orphaned even
when its parent got the event and exited.

Fix this by putting the child into a job object before it runs its
first instruction (created suspended, assigned, then resumed), so that
every descendant is in the job from the start; the teardown in Close
terminates the job right after initiating the pseudoconsole close.
There is no point in a grace period between the two: the close event
is not a graceful signal -- git and the common diff tools leave it to
the default handler, which calls ExitProcess at an arbitrary point --
so clients that received it are already dying, and the kill exists for
those that missed it. Killing at an arbitrary point cannot leak a
stale index.lock, because pty-rendered commands no longer take that
lock (see withPtyGitConfig in pkg/gui/pty.go).

The pseudoconsole close runs on its own goroutine because the kill
must not wait for it: on builds where ClosePseudoConsole blocks until
the console host exits (pre-24H2), the host keeps running as long as a
surviving client does, and that client only goes away through the job
kill; sequencing the kill after a blocking close would deadlock in
exactly the case the kill exists for.

KILL_ON_JOB_CLOSE doubles as a safety net: if lazygit exits without
running the teardown, the OS closes the job handle and reaps the tree.

In a harness that mimicked the stop path with randomized 0-120ms stop
delays, 3 of 30 process trees survived as orphans before this change;
none survive with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 07:03:27 +02:00
Stefan Haller d52c0a604f Give task commands a Terminate method instead of exposing their process
The task stop path terminates the still-running command by pulling its
*os.Process out of the Cmd interface and applying one global strategy
(TerminateProcessGracefully) to it. That shape can't accommodate the
upcoming fix for orphaned process trees on Windows: there, stopping a
pty task requires terminating the entire process tree via a job object
whose handle lives with the pty, not with the process. And the two Cmd
implementations genuinely need different strategies anyway: a
process-group kill (the likely future fix for #5675 on Unix) is only
safe for pty children, which run as session leaders, while plain
commands share lazygit's own process group.

So let each Cmd implementation decide how to terminate itself, and drop
GetProcess, which had no other callers. No change in behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 07:03:27 +02:00
Stefan Haller 9b22c7dc77
Prevent stale index.lock files from diffs rendered through a pty on Windows (#5888)
At the end of a diff against the worktree, git re-reads and refreshes
the index and writes it back if it found stale stat information
(diff.autoRefreshIndex, on by default). It holds index.lock for the
whole refresh; GIT_OPTIONAL_LOCKS does not cover this lock, and the
window scales with the size of the repository (~150ms for a 6k-file
repository with a warm stat cache).

On Windows, a pty task that is stopped because the user moved on
terminates its git process at an arbitrary point: tearing down the
pseudoconsole delivers CTRL_CLOSE_EVENT, which git leaves to the default
handler, which simply calls ExitProcess. If that lands inside the
refresh, a stale index.lock is left behind and the next git command
chokes on it. This is the same problem that 98801da106 fixed by no
longer killing git processes; the ConPTY support added in 0.63
reintroduced it through the close event.

Disable the automatic refresh for pty-rendered commands. They can afford
it: the refresh only persists refreshed stat information, and lazygit's
foreground git status refreshes -- which never run in a pty and are
never killed -- already write that back on every user action and on
terminal focus-in. The cost is that while the on-disk stat cache is
stale, an external differ is invoked even for files whose stat
information changed but whose content didn't, showing them as empty
diffs; this heals with the next foreground refresh, which also
re-renders the view.

Unix keeps the refresh: a stopped pty child gets SIGTERM there, and
git's signal handlers remove its lock files, so the lock window is
harmless. The rawGit renderer keeps it too: its tasks don't run in a pty
and are never killed on Windows -- they either run to completion or die
on a broken pipe mid-output, before the refresh begins.
2026-08-04 07:03:24 +02:00
Stefan Haller 0a92529b14 Prevent stale index.lock files from diffs rendered through a pty on Windows
At the end of a diff against the worktree, git re-reads and refreshes
the index and writes it back if it found stale stat information
(diff.autoRefreshIndex, on by default). It holds index.lock for the
whole refresh; GIT_OPTIONAL_LOCKS does not cover this lock, and the
window scales with the size of the repository (~150ms for a 6k-file
repository with a warm stat cache).

On Windows, a pty task that is stopped because the user moved on
terminates its git process at an arbitrary point: tearing down the
pseudoconsole delivers CTRL_CLOSE_EVENT, which git leaves to the
default handler, which simply calls ExitProcess. If that lands inside
the refresh, a stale index.lock is left behind and the next git
command chokes on it. This is the same problem that 98801da106 fixed
by no longer killing git processes; the ConPTY support added in 0.63
reintroduced it through the close event.

Disable the automatic refresh for pty-rendered commands. They can
afford it: the refresh only persists refreshed stat information, and
lazygit's foreground git status refreshes -- which never run in a pty
and are never killed -- already write that back on every user action
and on terminal focus-in. The cost is that while the on-disk stat
cache is stale, an external differ is invoked even for files whose
stat information changed but whose content didn't, showing them as
empty diffs; this heals with the next foreground refresh, which also
re-renders the view.

Unix keeps the refresh: a stopped pty child gets SIGTERM there, and
git's signal handlers remove its lock files, so the lock window is
harmless. The rawGit renderer keeps it too: its tasks don't run in a
pty and are never killed on Windows -- they either run to completion
or die on a broken pipe mid-output, before the refresh begins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 06:53:53 +02:00
Stefan Haller 6871959caa
Exclude more commit trailers from auto-wrapping (#5871)
We already excluded the most commonly used commit trailers from being
auto-wrapped when typing or rewording a commit message, but this was
limited to two hard-coded ones ("Signed-off-by:" and "Co-authored-by:").
Extend this mechanism to use a heuristic to prevent more trailers from
wrapping; the heuristic kicks in for any "Key: Value" line if Key
contains a dash, or the value looks like a URL (so that it also catches
things like "Bug: https://my-bug-tracker/345").

Fixes https://github.com/jesseduffield/lazygit/discussions/5869.
2026-08-02 19:25:14 +02:00
Stefan Haller b6deefacd2 Exclude more commit trailers from auto-wrapping
We already excluded the most commonly used commit trailers from being
auto-wrapped when typing or rewording a commit message, but this was
limited to two hard-coded ones ("Signed-off-by:" and "Co-authored-by:").
Extend this mechanism to use a heuristic to prevent more trailers from
wrapping; the heuristic kicks in for any "Key: Value" line if Key
contains a dash, or the value looks like a URL (so that it also catches
things like "Bug: https://my-bug-tracker/345").

To avoid mistaking a "Key: Value"-looking line in the message body for a
trailer, only apply the heuristic in the last paragraph of the message,
i.e. the block of lines at the end that is separated from the body by a
blank line. Each line there is judged on its own, so a line that isn't
recognized as a trailer still wraps without affecting the real trailers
next to it.
2026-08-02 19:22:34 +02:00
Stefan Haller 9811bc7ea8
Move Github PR cache out of state.yml into a separate file (#5884)
Having the cache in state.yml causes this file to be rewritten every
60s, which is annoying if you have a lazygit running in the background
somewhere without even realizing it, and it keeps overwriting the
foreground lazygit's newer command shell history and recent repos list
with its stale data. State.yml should only contain things that change in
response to user actions, not periodically.
2026-08-02 19:12:03 +02:00
Stefan Haller 47471c501b Move Github PR cache out of state.yml into a separate file
Having the cache in state.yml causes this file to be rewritten every
60s, which is annoying if you have a lazygit running in the background
somewhere without even realizing it, and it keeps overwriting the
foreground lazygit's newer command shell history and recent repos list
with its stale data. State.yml should only contain things that change in
response to user actions, not periodically.
2026-08-02 19:09:22 +02:00
Stefan Haller 669c445eb6
Show a Github PR's combined checks state in branches list (and main view for selected branch) (#5874)
For a while now we have been showing Github icons in the branches panel
to indicate which branches have an associated pull request. This PR
changes this so that open PRs (which used to show a green Github icon)
now show a colored symbol indicating the combined state of the Github
checks (if any are configured for this repo):

<img width="1046" height="418" alt="image"
src="https://github.com/user-attachments/assets/3e9074a6-0ae4-423a-90d9-306863daa46c"
/>

This lets you see at a glance which of your PRs are ready to merge.
(Note however that this feature knows nothing about which of the checks
are _required_ for merging, so a branch showing a failure icon may still
be ready to merge if the failing checks are not required ones.)

It may be a little confusing for new users to see two green checkmarks
for a branch, one to the left of the name and one to the right; they
mean very different things. Still, seeing the Github checks state in the
branches list is so useful that I think it's a good compromise;
hopefully users will learn quickly enough what all the icons mean.

When selecting a branch that has a pull request, the main view already
showed some information about the PR (its title, pr number, and the
state (open/closed/merged). This PR adds an aggregated pull request
checks field to this, indicating the overall checks state
(passed/failed/pending/...). It has a hyperlink taking you directly to
the checks tab on Github.

<img width="1538" height="246" alt="CleanShot 2026-07-30 at 12 32 25@2x"
src="https://github.com/user-attachments/assets/68aa24da-d78a-45d7-8812-db8b509bbbc8"
/>

The update frequency of the checks badge is tied to the auto-fetch
cadence for now (once every minute by default); you can fetch manually
to force an update, and some user actions (including the focus-in
refresh) update the PR states too, but anything more fancy (e.g.
fetching just the checks state of the selected branch) would have been
more work, and I want to see if this is good enough.
2026-08-02 19:08:25 +02:00
Stefan Haller 2669842445 Show a Github PR's combined checks state in branches list and main view
In the branches list we show the checks icon (✓, ✗ etc) instead of the
gihub icon for branches that are open and have a state. It is a little
confusing, because the ✓ in front of the name means something very
different than the ✓ after it, but the checks status is just too useful
to see in the list.

In the main view we show it as a compact status before the PR title,
with a hyperlink that takes you directly to the checks tab in Github.
2026-08-02 13:06:21 +02:00
Stefan Haller 320d33a8ef Centralize pull-request header presentation
The branch controller should decide which pull request to show, not how its
header is styled and linked. Move the existing formatter and state badge next
to the branch presentation helpers so subsequent header changes stay in one
layer.
2026-07-31 09:01:44 +02:00
Stefan Haller ff26f61ffd Carry aggregate check state with GitHub pull requests
GitHub exposes a combined status for the head commit without requiring
individual check contexts. Include that rollup in the existing request
and startup cache so every consumer sees the same state without making a
second network request.
2026-07-31 09:01:44 +02:00
Stefan Haller 67ec23e08a Isolate GitHub pull-request response parsing
The fetch currently combines transport, JSON decoding, and model
conversion, which makes response changes difficult to verify without
exercising the network. Put the deterministic work behind a small parser
so later payload changes can be covered with raw GraphQL fixtures.
2026-07-31 09:01:44 +02:00
Stefan Haller d8d09e1f94
Rework the custom pager config (rename to diff renderer) (#5870)
For a long time lazygit has used the term "custom pager" to refer to
what's really a "diff renderer". A pager is a program that allows you to
view output page by page (hence the name), e.g. less; lazygit's custom
diff renderers are not pagers. It used the term only because the feature
is implemented using git's `GIT_PAGER` env var, but that's an
implementation detail.

Rename the 'git.pagers' config to 'git.diffRenderers', and restructure
its elements while we're at it to make things clearer:

- Add a 'type' field to explicitly specify which type of diff renderer
it is (the two fundamentally different ones are 'stdinFilter' and
'extDiff').
- Add a third type, 'rawGit', which has an 'args' field that makes it
easy to use 'git diff --color-words' as a custom renderer
- Unify the old 'pager' and 'externalDiffCommand' fields to a single
'command' field for both types

Existing config files are migrated automatically.
2026-07-31 08:58:48 +02:00
Stefan Haller e296845aae Adapt links 2026-07-31 08:42:51 +02:00
Stefan Haller 4598c7b548 Adapt documentation 2026-07-31 08:42:51 +02:00
Stefan Haller 8731d8a51b Rework the custom pager config (rename to diff renderer)
For a long time lazygit has used the term "custom pager" to refer to
what's really a "diff renderer". A pager is a program that allows you to
view output page by page (hence the name), e.g. less; lazygit's custom
diff renderers are not pagers. It used the term only because the feature
is implemented using git's GIT_PAGER env var, but that's an
implementation detail.

Rename the 'git.pagers' config to 'git.diffRenderers', and restructure
its elements while we're at it to make things clearer:

- Add a 'type' field to explicitly specify which type of diff renderer
  it is (the two fundamentally different ones are 'stdinFilter' and
  'extDiff').
- Add a third type, 'rawGit', which has an 'args' field that makes it
  easy to use 'git --color-words' as a custom renderer
- Unify the old 'pager' and 'externalDiffCommand' fields to a single
  'command' field for both types

Existing config files are migrated automatically.
2026-07-31 08:42:51 +02:00
Stefan Haller ac4a1f575c Fix tests to use the right field name
It doesn't matter for what this test used to test (that nothing changes
if the 'pagers' array exists), but it will be relevant once we further
migrate the 'pagers' array from there.
2026-07-31 08:42:51 +02:00
Stefan Haller 1cf632002e Make CurrentPagerName always return a name
We don't want callers to need any additional logic, so pass in the
translation set so that the function can decide what static text to
return. This allows us to get rid of the CurrentPagerUsesGitConfigDiff
method which is in the way for the refactoring we're about to do.
2026-07-31 08:42:51 +02:00
Stefan Haller 2694aa774a Extract GitCommandBuilder method AddCommonDiffArgs
We were setting the same arguments in five places; extract this to one.
2026-07-31 08:42:51 +02:00
Stefan Haller 1ebb65b775 Cleanup: add blank lines for canonical markdown
My markdown formatter in VS Code adds these when I press Command-S.
2026-07-31 08:42:51 +02:00
Stefan Haller 4479d34e37 Remove BenchmarkMigrationOnLargeConfiguration
It has served its purpose when config migration was initially
implemented, but nobody runs this benchmark nowadays, and the example
config has run out of date with reality. Some PRs have still updated it
when they made changes to the config, but others didn't, and it's
unclear what the rules are; so let's just remove it.
2026-07-31 08:42:51 +02:00
Stefan Haller 91dcd4cbb3
Reorder commits (or rebase todos) by dragging with the mouse (#5857)
Pressing the left button on the current selection now starts a drag that
moves the selected commits, both in the normal commits view and for
todos during an interactive rebase. A press on an unselected row still
creates a range selection.

While dragging, a "drop here" indicator shows where the commits will be
inserted.

Moving commits with the mouse is useful for the case that you want to
move them a longer distance, because that's slow when doing it one by
one with the keyboard, and also you don't want to resolve conflicts at
every step. The standard workaround for that is to enter an interactive
rebase first and then continue it afterwards, but that's a bit
cumbersome; dragging solves that nicely.

Closes #5842.
2026-07-31 08:41:18 +02:00
Stefan Haller 104fdf34a9 Keep the destination visible while commits move
Moving commits runs a rebase, which can take a while. Instead of
letting the drop indicator vanish the moment the button is released,
keep it in place and turn it into a "moving commits here" spinner once
the move takes longer than a short grace period, so that quick moves
stay free of flicker. The indicator is cleared when the post-move
refresh lands.
2026-07-31 08:37:28 +02:00
Stefan Haller cefec1c5c9 Cancel commit drags with escape
While a commit drag is in progress, escape now aborts it: the drag
state and the drop indicator are discarded and the mouse capture is
released, so nothing happens when the button is eventually released.
Otherwise escape keeps its normal meaning.
2026-07-31 08:37:28 +02:00
Stefan Haller 0738d55551 Keep scrolling while commits are dragged at an edge
Reuse the drag autoscroller for commit drags. Scrolling stops once the
insertion point reaches the end of the allowed range in the scroll
direction, so during a rebase the view doesn't keep scrolling once the
last insertion position among the todos has been reached.
2026-07-31 08:37:28 +02:00
Stefan Haller eda4ad1192 Drag selected commits to a new position
Pressing the left button on the current selection now starts a drag
that moves the selected commits, both in the normal commits view and
for todos during an interactive rebase. A press anywhere else falls
through to the usual click handling, so dragging from an unselected
line still creates a range selection, and releasing without having
moved collapses the selection to the pressed commit like a plain click
would.

While dragging, the insertion point follows the pointer: rows below
the dragged block insert after the pointed-at commit, rows above it
insert before it, and during a rebase the destination is limited to
the contiguous block of movable todos around the selection. gocui
moves the view cursor along with the pointer, so each drag event moves
it back to keep the original selection highlighted.

The move happens on release. The model may have been refreshed during
the drag, so the dragged commits are located again by their identity
(hash, subject, todo action); if they no longer form a unique
contiguous block, the drop is ignored rather than guessing.
2026-07-31 08:37:28 +02:00
Stefan Haller b055d28fb1 Show commit insertion points during a drag
Render the insertion point of a commit drag as a non-model item in the
commits list. It must be inserted at the right position relative to
the section headers, because the list renderer assumes non-model items
are ordered by their model index.

Not used yet, we'll hook it up to the drag gesture in the next commit.
2026-07-31 08:37:28 +02:00
Stefan Haller e756511042 Move dragged commits in one rebase
Let the todo-move primitives take a distance instead of hardcoding a
single row, by iterating the one-row move in memory. Dropping a commit
several rows away thus rewrites the todo file once and, outside of an
interactive rebase, runs a single rebase rather than one per row.
2026-07-31 08:37:28 +02:00
Stefan Haller b85483ecc0 Unify commit movement before adding drag destinations
Merge the up/down variants of the move commands into one
direction-parameterized implementation. Dragging commits is about to
need moves over arbitrary distances, which we don't want to build twice.
2026-07-31 08:37:28 +02:00
Stefan Haller 4085b6462d
Create a range selection in list views by dragging with the mouse (#5856)
Like in the staging view, drag with the mouse in a list view to create a
non-sticky range selection (including auto-scrolling as the mouse
reaches the view edge).
2026-07-31 08:37:13 +02:00
Stefan Haller 14434015c7 Keep range drags moving at panel edges
Give the list views the same edge autoscroll during drag selection
that the staging view already has; the new mouse-release binding stops
the autoscroll when the drag ends.
2026-07-31 08:32:43 +02:00
Stefan Haller e7998356be Create a range selection by dragging with the mouse in list views
Dragging with the left button held now extends the selection from the
pressed line, exactly like moving with shift+up/down does. We use the
non-sticky flavor so that the range collapses on the next plain cursor
movement, again matching the keyboard behavior.

The binding is only registered for contexts that support range selection
in the first place; dragging in other lists continues to do nothing.
2026-07-31 08:32:43 +02:00
Stefan Haller 10bac9dbf6
Auto-scroll when dragging to create range selection in staging view (#5855)
The staging view (and custom patch building view) already has the
capability to create a range selection by dragging with the mouse;
however, a longer range couldn't be selected this way because the
dragging stopped at the view edge. Add auto-scrolling so that the view
scrolls as the mouse reaches the view edge; slowly on the innermost edge
row, faster on the outermost row and beyond. Scrolling starts after a
short delay so that a drag merely passing near the edge doesn't scroll.
2026-07-31 08:32:25 +02:00
Stefan Haller b682fb7635 Auto-scroll when dragging to create range selection in staging view
When the pointer reaches the edge of the view during a drag (or leaves
the view entirely, which mouse capture makes possible), keep scrolling
and extending the selection: slowly on the innermost edge row, faster
on the outermost row, and very fast beyond. Scrolling starts after a
short delay so that a drag merely passing near the edge doesn't scroll.

When the view loses focus mid-drag (e.g. because a popup appeared),
cancel the autoscroll and the mouse capture.
2026-07-31 08:26:35 +02:00
Stefan Haller f5a069342b Add DragAutoScroller helper
It can be used to auto-scroll a view during drag gestures when the
cursor reaches the edge of the view.
2026-07-31 08:26:35 +02:00
Stefan Haller ba024ed6c9 Add ViewDriver methods for mouse drag gestures in integration tests
Add press/move/release primitives next to the existing Click. The test
driver remembers the last reported position so a release doesn't have
to repeat the coordinates, and RepeatMouseMove lets a test verify that
a held-button motion event within the same cell has no effect.
2026-07-31 08:26:35 +02:00
Stefan Haller 98613957ae Refactor: extract replayMouseEvent helper method 2026-07-31 08:26:35 +02:00
Stefan Haller a199a0a680
Gocui mouse event fixes (#5854)
Some fixes to gocui mouse event handling; these don't fix current bugs
in lazygit, but they are needed for upcoming work involving drag
gestures.
2026-07-31 08:26:16 +02:00
Stefan Haller 4609985029 Route mouse events to their originating view during a drag gesture
Route all mouse events to the view that was under the pointer when the
left button was pressed, until the button is released. Previously each
event went to whatever view was under the pointer at the time, so a
drag that left the view's bounds started acting on neighboring views.

Since events can now carry positions outside the view, clamp the view
cursor to the view's bounds in that case (handlers still receive the
unclamped position), and require an actual click for tab activation so
that a captured drag crossing the tab row doesn't switch tabs.
2026-07-31 08:22:35 +02:00
Stefan Haller 44a2bbeb7c Deliver mouse release after a drag
Releasing a mouse button was delivered as a plain mouse-move (hover)
event: the release processing resets dragState to NOT_DRAGGING, after
which the event fell into the NOT_DRAGGING branch. Views therefore had
no way of telling that a drag gesture ended, which the upcoming
drag-based features (range selection, commit reordering) need.

Deliver the release as a real mouse event with the MouseRelease key
and normalize its modifiers to ModNone, so release bindings also match
modified drags. Make recordClickInfo ignore it: a release is the end of
a click, not a click of its own, and must not break double-click
detection.
2026-07-31 08:22:35 +02:00
Stefan Haller 38d2293a10 Add test for double-click detection
Add a test pinning down that a press/release/press sequence at the
same position is detected as a double click. An upcoming commit starts
delivering the release as a real mouse event to the click-recording
code, which must not mistake it for a click of its own.
2026-07-31 08:22:35 +02:00
Stefan Haller a965db2a7d Demonstrate that drag release becomes hover 2026-07-31 08:22:35 +02:00
Stefan Haller ff53a3ed8c Preserve the first mouse movement of a drag
When the left button is pressed and the pointer then moves, the event
that made the MAYBE_DRAGGING -> DRAGGING transition fell through the
switch without being assigned a key or modifier, so the first cell of
every drag arrived at handlers as a MouseRelease event without the
motion modifier and was effectively lost. Give it the same
MouseLeft/ModMotion identity as all subsequent drag events.

Held-button motion events that stay within the pressed cell carry no
information at all; swallow them instead of letting them through as
further release-shaped events (which used to clobber the double-click
state when the pointer jittered within a cell between two clicks).
2026-07-31 08:22:35 +02:00
Stefan Haller c5fe27dfa5 Additions to AGENTS.md 2026-07-31 08:22:35 +02:00
Stefan Haller 5e0a2e97ef
Some small UI polish (#5853)
Two small UI changes:
- Change list section headers to a slightly more elegant design
- Change default spinner frames and rate
2026-07-31 08:22:22 +02:00
Stefan Haller 31cbabe48f Change list section headers to a slightly more elegant design
I'm not a skilled UI designer, so I suspect there may be even better
options, but it's definitely already better than the raw ASCII "---" we
had before.

Put the line only at the beginning because it looks bad if the line
after the label is misaligned when labels don't have the same width
(e.g. "Remote" vs. "Local" in the divergence view).
2026-07-31 08:03:40 +02:00
Stefan Haller 22db808a01 Add a helper function for formatting list section headers
We want to change their design, and we want to do it only in one place.
2026-07-31 08:03:40 +02:00
Stefan Haller 67c81c5b14 Change default spinner frames and rate
I find that this looks a little nicer, and it reduces CPU load by not
spinning so fast.
2026-07-31 08:03:40 +02:00
Stefan Haller 940d7cc80f
Support absolute paths when detecting edit preset from EDITOR env var (#5876)
Fixes #5875.
2026-07-31 08:01:57 +02:00
Stefan Haller f15f713c68 Support absolute paths when detecting edit preset from EDITOR env var 2026-07-30 17:31:56 +02:00
Stefan Haller df0943ad33
Fix multi-selection of files with common prefix not working in commit files panel (#5868)
When selecting sibling directories sharing a common prefix, trying to
add them to a custom patch or discarding them from the commit would only
add one, not both. We had the same bug in the files panel and fixed it
in #3599, but forgot to make the equivalent change in the commit files
panel.

Fixes #5866.
2026-07-28 18:39:49 +02:00
Stefan Haller 1d107721f2 Fix multi-selection of files with common prefix not working in commit files panel
Equivalent to the change that was made to isDescendentOfSelectedNodes in
files_controller.go in a5eec48b4b.
2026-07-28 18:14:58 +02:00
Stefan Haller c7acf38399 Make isDescendentOfSelectedCommitFileNodes work for the root item
The root item's path is ".", and the path of a file at top level is
"./file". When using GetPath, this gives us "." and "file",
respectively, and isDescendentOfSelectedCommitFileNodes would return
false for these.

Working with the internal paths (i.e. without stripping the leading
"./") fixes this.

There is no known breakage that is caused by this, that's why I'm not
adding an integration test that demonstrates a bug.

Equivalent to the change that was made to isDescendentOfSelectedNodes in
files_controller.go in 302b621b68.
2026-07-28 18:14:58 +02:00
Stefan Haller 8fefe2b933 Cleanup: move variable assignment out of the loop
It never changes inside this function, so there's no need to recompute
it with every loop iteration.

Equivalent to the change that was made to isDescendentOfSelectedNodes in
files_controller.go in d0c6e27fee.
2026-07-28 18:04:35 +02:00
Stefan Haller 6a022241d2 Add test to demonstrate a problem with custom patches and directories sharing a prefix
We had the same bug in the files panel, and fixed it in a5eec48b4b, but
forgot to make the equivalent change to the commit files panel.
2026-07-28 18:04:35 +02:00
Stefan Haller 292035709f
Small addition to CONTRIBUTING.md (#5861) 2026-07-25 18:39:03 +02:00
Stefan Haller 9d7ca51ee7 Small addition to CONTRIBUTING.md 2026-07-25 18:36:28 +02:00
Stefan Haller 338d017191
Fix clicking in commits panel of unfocused VS Code window (#5859)
When lazygit is running in a VS Code window that doesn't have the focus
(e.g. a split tab) and you click in the commits panel to focus it and
select a commit at the same time, it would briefly select the commit you
clicked but then flash back to the commit that was previously selected.
This PR fixes that so that the clicked commit stays selected.

This was only a problem with VS Code; in Zed's builtin terminal it
worked, apparently because it first dispatches the click and then the
focus-in event. Ghostty and iTerm2 were not affected because they don't
dispatch clicks in inactive windows or tabs at all.

Labelling as ignore-for-release because it fixes a regression that was
introduced since the last release.
2026-07-25 18:09:39 +02:00
Stefan Haller 3d9318e2a7 Preserve commit clicks during focus refreshes
This fixes the problem described in the previous commit; we no longer
capture the selection at the start of the refresh. There's no reason to
do that (we don't do it for branches either). It is enough to capture
the selection in the final bounce, before we assign the new model slice.
2026-07-24 15:32:19 +02:00
Stefan Haller 5aa003612c Demonstrate stale focus refresh overwriting a click
When clicking in the commits view of lazygit running in an unfocused VS
Code window, VS Code first sends us the focus-in event and then the
mouse-click. The focus-in refresh captures the selection when it starts,
then we handle the mouse click and you briefly see the clicked row
getting selected, but then the selection flashes back to the original
row as the refresh restores it when done.
2026-07-24 15:30:29 +02:00
Stefan Haller a5b4477a20
Fix Windows crash when switching to fullscreen mode with a custom pager (#5838)
Fix a crash when typing `+` twice to go to full screen mode, when a
custom pager such as delta or difftastic is configured.

Fixes #5837.
2026-07-23 18:07:56 +02:00
Stefan Haller 02c8ba3073 Clamp ConPTY sizes to the 1x1 minimum that Windows accepts
CreatePseudoConsole and ResizePseudoConsole reject zero dimensions with
E_INVALIDARG, but we legitimately request them: the pty is sized after
the main view, and that view is zero-sized while hidden, e.g. in
full-screen mode with a side panel focused. Entering that mode while a
custom pager is configured therefore made StartPty fail (degrading to
unpaged output now that the fallback works), and resizing a live pty
from onResize would fail layout. The Unix pty accepts zero sizes, so
the clamp lives in the Windows implementation only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:31:26 +02:00
Stefan Haller c217084c90 Add test showing StartPty fails on Windows when given a zero size
CreatePseudoConsole rejects zero dimensions with E_INVALIDARG, so
starting a pty sized after a hidden (and thus zero-sized) view fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:31:26 +02:00
Stefan Haller f000ce9f1c Never hand NewCmdTask a nil reader when a command fails to start
NewCmdTask feeds the reader returned by its start func into a
bufio.Scanner, and Scanner.Scan panics with a nil pointer dereference
when that reader is nil. Two start funcs could produce one:

- newPtyTask's fallback for a failed StartPty returned a literal nil
  reader, alongside an ExecCmd that was never started, so the intended
  "fall back to a plain cmd task" never worked. This crashed lazygit on
  Windows when using a custom pager with the main view zero-sized, e.g.
  after pressing + twice to enter full-screen mode with a side panel
  focused: ConPTY rejects zero dimensions, making StartPty fail.

- startCmdWithPipe returned nil when the pipe couldn't be created,
  which the Unix pty fallback path can trigger, since a failed pty
  start can leave the tty assigned to the command's stdout.

Make startCmdWithPipe never return a nil reader: when the pipe can't be
created, don't start the command at all and return an empty reader so
the task shuts down cleanly with the error in the log. Then route
newPtyTask's fallback through it, so a StartPty failure degrades to
running the command without a pty: the pager is lost, but the command's
output still renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:31:26 +02:00
Stefan Haller 400faea60c Add test showing startCmdWithPipe returns a nil reader on pipe failure
NewCmdTask feeds the reader returned by its start func straight into a
bufio.Scanner, whose Scan panics on a nil reader with a nil pointer
dereference. startCmdWithPipe returns exactly that when the pipe cannot
be created.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:31:26 +02:00
Stefan Haller 5209294a56 Extract helper for starting a command with piped output
The fallback path in newPtyTask (taken when StartPty fails) needs the
same start-the-command-with-a-pipe logic that newCmdTask uses, so pull
it out into a helper that both can share. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:31:26 +02:00
Stefan Haller 1dc7d4f589
Support a {{diffContext}} template variable in external diff command (#5841)
Useful for passing the context size to external diff commands like
difftastic, like so:

```yml
git:
  pagers:
    - externalDiffCommand: difft --color=always --context={{diffContext}}
```

See
https://github.com/jesseduffield/lazygit/issues/5527#issuecomment-5026444795.
2026-07-23 17:29:22 +02:00
Stefan Haller 87d9537de9 Support a {{diffContext}} template variable in external diff command
Useful for passing the context size to external diff commands like
difftastic.
2026-07-23 17:13:02 +02:00
Stefan Haller d8b07ee4f5
Add RefreshBlockingInput to buffer keypresses until a refresh has landed (#5844)
A refresh from the UI thread returns immediately and applies its model
and view updates as queued UI-thread callbacks. A key pressed before
those have run is handled against the stale, pre-refresh state. For most
keys that's harmless, but some handlers turn that state into git
commands: pressing space twice in quick succession in the staging panel
builds the second patch from the already-applied diff and fails with
'patch does not apply', because the refresh after the first press is
what moves the selection to the next stageable hunk.

Notably, this is not just a regression of the recent change that made
UI-thread refreshes non-blocking; the window was merely much narrower
before. A blocking refresh parked the UI thread while the scopes'
bounces were queued, and the event loop drains pending keyboard input
with priority over queued user events, so a key pressed during the
blocked window still beat the queued state updates. The guarantee that
the next keypress sees post-refresh state had already ended when the
scopes' state updates moved from worker-side mutex-guarded writes to
UI-thread bounces.

Fix it with the input-blocking mechanism we already use for commit
surgery, exposed as a new RefreshBlockingInput entry point: it begins
blocking events synchronously in the calling handler, and ends the block
from a callback that the finishing step queues behind the refresh's own
updates. Keys pressed while the refresh is in flight are buffered and
replayed, in order, against the fully refreshed state; since a replayed
key's handler re-enters this same path, a burst of keypresses applies
sequentially, each one seeing the previous one's refresh. Unlike the old
blocking refreshes, this doesn't freeze the UI thread: rendering,
spinners, resizing, and mouse scrolling keep working while input is
withheld.

Blocking input is opt-in per call site rather than the default for all
UI-thread refreshes, because most refreshes (the focus-in and startup
refreshes, say) don't produce state that the next keypress depends on,
and blocking on them would delay typing for no reason. It should also be
limited to quick, narrow-scoped refreshes: a full refresh, or any scope
that pulls in COMMITS, can take very long in large repos and should
usually not hold up input.

Use the new function for the staging panel's stage/discard/edit-hunk
refreshes, for moving rebase todos, and for stash operations.

Labelling as ignore-for-release because it fixes regressions introduced
after the last release.
2026-07-22 09:05:38 +02:00
Stefan Haller 975da9b8a9 Block input and batch UI updates when switching repos
On startup we don't want to block input during the initial refresh (it
should be possible to press, say, `4` to jump to the commits panel right
after startup without a delay), and we also want panels to show their
contents as soon as possible; it doesn't matter so much that it's not in
sync, we go from empty to populated here. However, when switching repos
it can be confusing that some panels that are slow to update still show
the old repo's data while others already show the new one's data, so
update the UI only when everything is ready, and also block input to
prevent accidentally trying to act on the old, stale data.
2026-07-22 08:31:11 +02:00
Stefan Haller fc975f32a8 Block input while the refresh after a stash operation is in flight
Popping or dropping a stash shifts the indices of the entries below it,
and renaming re-creates the stash at the top, shifting all the others.
The stash model is only rebuilt by the refresh, which finishes in the
background, so acting on the next entry in quick succession — pressing
the key, confirming the popup, and pressing again right away — reads the
stale pre-operation indices and targets the wrong stash. Note that the
confirmation popup is no protection here: the race starts when the
confirm handler runs, and the next keypress can easily beat the refresh.

Use RefreshBlockingInput so a quick follow-up keypress is buffered and
replayed once the refreshed stash list is in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 08:31:11 +02:00
Stefan Haller 055184f997 Block input while the refresh after moving a rebase todo is in flight
Moving a todo rewrites the todo file and advances the selection
synchronously, but the commits model is only rebuilt by the refresh. A
second press arriving before that grabs the swapped-with todo from the
stale model at the advanced index and moves it back, so holding the key
to move a todo several slots misbehaved. Use RefreshBlockingInput so the
second press is buffered and replayed once the moved todo list is in
place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 08:31:11 +02:00
Stefan Haller 200042a57c Add test showing that rapidly moving a rebase todo twice moves the wrong todo
Moving a todo up or down rewrites the todo file and advances the
selection synchronously, but the commits model is only rebuilt by the
refresh, which finishes in the background. A second keypress arriving
before that reads the pre-move model at the advanced selection index —
that's the todo the first move swapped with, so the second press moves
that one back instead of moving the selected todo further. Two rapid
presses (e.g. from holding the key down) thus amount to a net no-op.

The two presses also spawn two racing refreshes whose model updates can
land in either order, so the todo list can even end up disagreeing with
the todo file. That's why the test continues the rebase and asserts the
resulting commit order instead of the displayed list: the rebase replays
the file, which is deterministic.

The test documents this currently broken behavior; the fix comes next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 08:31:11 +02:00
Stefan Haller b96b8a9753 Add RefreshBlockingInput to buffer keypresses until a refresh has landed
A refresh from the UI thread returns immediately and applies its model
and view updates as queued UI-thread callbacks. A key pressed before
those have run is handled against the stale, pre-refresh state. For most
keys that's harmless, but some handlers turn that state into git
commands: pressing space twice in quick succession in the staging panel
builds the second patch from the already-applied diff and fails with
'patch does not apply', because the refresh after the first press is
what moves the selection to the next stageable hunk.

Notably, this is not just a regression of the recent change that made
UI-thread refreshes non-blocking; the window was merely much narrower
before. A blocking refresh parked the UI thread while the scopes'
bounces were queued, and the event loop drains pending keyboard input
with priority over queued user events, so a key pressed during the
blocked window still beat the queued state updates. The guarantee that
the next keypress sees post-refresh state had already ended when the
scopes' state updates moved from worker-side mutex-guarded writes to
UI-thread bounces.

Fix it with the input-blocking mechanism we already use for commit
surgery, exposed as a new RefreshBlockingInput entry point: it begins
blocking events synchronously in the calling handler, and ends the
block from a callback that the finishing step queues behind the
refresh's own updates. Keys pressed while the refresh is in flight are
buffered and replayed, in order, against the fully refreshed state;
since a replayed key's handler re-enters this same path, a burst of
keypresses applies sequentially, each one seeing the previous one's
refresh. Unlike the old blocking refreshes, this doesn't freeze the UI
thread: rendering, spinners, resizing, and mouse scrolling keep working
while input is withheld.

Blocking input is opt-in per call site rather than the default for all
UI-thread refreshes, because most refreshes (the focus-in and startup
refreshes, say) don't produce state that the next keypress depends on,
and blocking on them would delay typing for no reason. It should also be
limited to quick, narrow-scoped refreshes: a full refresh, or any scope
that pulls in COMMITS, can take very long in large repos and should
usually not hold up input.

The staging panel's stage/discard/edit-hunk refreshes use it now.
2026-07-22 08:31:11 +02:00
Stefan Haller 963db76ab6 Add test showing that a rapid second keypress acts on a stale staging panel
Pressing space twice in quick succession in the staging panel is supposed
to stage two hunks: the refresh triggered by the first press rebuilds the
panel's diff and moves the selection to the next stageable hunk, and the
second press stages that.

Since we made UI-thread refreshes non-blocking, the second press is
handled as soon as it arrives, while that refresh is still in flight. It
then reads the stale pre-refresh diff, builds the first hunk's patch
again, and git apply fails with 'patch does not apply' because those
lines are already in the index.

The test documents this currently broken behavior; the fix comes next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 22:23:58 +02:00
Stefan Haller 7a902b56cc Add a way for integration tests to press keys in rapid succession
The test driver waits for lazygit to become idle after every keypress, so
tests could never exercise what happens when a key arrives while the
previous key's processing is still in flight — for example while the
refresh triggered by the previous key hasn't updated the model yet. Real
users type faster than that all the time.

PressRapidly injects all its keys back to back and waits for idle only
once at the end, so the second and later keys are queued before the first
one's processing has finished. The next commit uses this to demonstrate a
bug in exactly that scenario.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 22:23:58 +02:00
Stefan Haller 2e653ceeba Update comments that still describe the removed blocking refresh mode
A few comments still reasoned in terms of SYNC vs ASYNC refreshes, a
distinction that no longer exists: sync vs async is now derived from the
calling thread. Restate them in terms of the current mechanisms
(RefreshFromWorker blocking its worker, model updates being enqueued on
the UI thread) without changing any behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 22:23:58 +02:00
Stefan Haller 00cef799ce Show the inline status again when checking out a newly created remote branch
Checking out a remote branch that has no local counterpart creates the
local branch, refreshes, and then checks it out. The refresh exists so
that CheckoutRef finds the new branch in the model and attaches an inline
status to the branch item instead of showing a global waiting status. But
since UI-thread refreshes stopped blocking, the checkout started before
the refreshed branches had landed in the model, so the lookup failed and
we always got the waiting status. Run the checkout from the refresh's
Then, which is queued behind the model update.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 22:23:57 +02:00
Stefan Haller 4b3e5f123f
Fix more problems related to concurrent repo switch and background refresh (#5839)
Make the background fetch and the refresh that runs after it more safe
against racing with a concurrent foreground repo switch (i.e. switching
worktrees, repos, or submodules). This fixes a bunch of different
problems; see the individual commit messages for details.
2026-07-21 18:40:28 +02:00
Stefan Haller 5fc678dde6 Read the gui's per-repo pointers on the UI thread in background routines
gui.git, gui.helpers and gui.State are all replaced on a repo switch,
which runs on the UI thread. The background fetch and the external-
change poller read them from their own goroutines, racing the
reassignment. This race can't show up in the integration suite, which
doesn't enable the background routines, so no -race run will ever flag
it; it can only bite real users who switch repos while a background
fetch or poll is in flight.

Capture the objects a routine iteration needs in a single blocking
UI-thread hop before using them, the same pattern the refresh's input
capture uses. For the fetch this has two welcome side effects: the
fetch, the post-fetch refresh's generation baseline, and the recorded
fetch time now all refer to the same repo (the old comment documented
the timestamp's mismatch as a known, unguarded race), and the git
instance the fetch runs through is pinned to that repo's directory, so
a switch mid-fetch can no longer direct in-flight work at the new repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller efed9d0407 Don't auto-forward branches when the repo was switched during the fetch
PostFetchRefresh's refresh is the only background refresh carrying a
Then callback, and Then callbacks are not generation-guarded: when the
background fetch's refresh crossed a repo switch, the callback still
ran — in the new repo — and auto-forwarded the new repo's branches
because the old repo's fetch had completed. That was harmless in
practice (the update-ref call compares against the expected old value,
and it only does what the next fetch's auto-forward would do anyway),
but mutating refs in a repo whose fetch never happened is not an action
the user took. Skip the auto-forward when the repo generation changed
since the fetch started.

The generation is captured by the fetch's callers before the fetch
runs, not by PostFetchRefresh itself: the background fetch doesn't
block repo switching and is a network call, so by the time
PostFetchRefresh runs a switch may already have happened — a capture
there (or the one the refresh itself takes) would compare against the
new repo's generation and let the auto-forward through. For the manual
fetch the capture point makes no difference, since a foreground
operation blocks repo switching for its entire duration.

This deliberately guards only this call site rather than making Then
callbacks generation-guarded in general: a Then is an arbitrary
callback, and whether it is safe to skip on a repo switch is a decision
for the author of the call site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 568a4276d7 Pin the cached git config's commands to the repo directory
The cached git config runs its `git config` reads through raw
exec.Command calls, outside the pinned git command builder, so they
followed the process working directory. A cache miss on a stale
instance — one still in use by a refresh that crossed a repo switch —
would therefore read the new repo's local config while computing data
for the old one. Give the cache a directory, set once by NewGitCommand
right after it determines the repo paths (the object is created fresh
for every repo switch, so no cross-repo cache invalidation is needed),
and run every config command there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller e0b8dbf48c Resolve the refresh's file reads against its repo root
The refresh workers read a few files at paths relative to the process
working directory: the submodule config read of .gitmodules, the files
refresh's check for conflict markers, and the submodule stash's
existence check. Git commands are pinned to the repo their instance was
created for, but these Go file reads still followed the cwd, so a
background refresh crossing a repo switch would read the new repo's
files while computing data for the old one. Join them with the worktree
root of the instance they belong to. (Most git-state file reads —
working tree state, rebase todos, bisect info — already resolve
against RepoPaths and need no change.)

This also fixes the submodule stash's existence check for nested
submodules: it stat'ed submodule.Path, which is relative to the parent
module, against the repo root — now it uses the submodule's full path,
matching the stash command right below it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller ae095f276b Don't refuse a repo switch during a pure refresh
The refreshes on focus-in, right after a repo switch, and after
returning from a subprocess are full foreground refreshes, so their
tasks kept Busy() true for as long as the slowest scope took — and any
switch attempt in that window was refused with the "can't switch"
toast. The focus-in one is particularly annoying: focusing lazygit is
often precisely what the user does in order to switch repos, and right
after regaining focus is when a refresh takes longest.

Blocking the switch bought nothing there. The refusal exists for user
operations, whose follow-up work (e.g. a Then callback reading the
model) isn't covered by the switch-safety guards; but these refreshes
merely reload state, and a refresh by itself is now switch-safe: its
git commands run against the repo it was started for, and the
generation guard drops its updates when the repo changed.

We can't just mark them Background, because that flag also decides
whether the files refresh lets git take optional locks to persist its
refreshed stat cache — worth doing for an attended refresh, and the
focus-in refresh (typically running right after external changes) is
the case that profits most. So split the two meanings: a new
DontBlockRepoSwitch option dispatches the refresh's tasks as background
tasks (excluded from Busy()) while keeping the attended optional-locks
behavior. Combining it with Then panics, since Then is not
generation-guarded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 8e045653be Don't pop up errors from a refresh worker once the repo was switched
An error returned from a gocui worker is shown to the user in an error
popup. For the branch loader's behind-counts worker that used to be the
"no such ref" popup when a background refresh crossed a repo switch:
the old repo's main branch didn't exist in the new repo. The previous
commits fix that scenario properly — the command now runs against the
repo the refresh was started for — but a stale worker can still fail
legitimately, most plausibly because that repo was deleted after
switching away from it (e.g. removing a worktree). Its results are
dropped anyway, so log the error instead of alarming the user about a
repo they already left.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 7c0fa9fe33 Run a refresh's git commands through the instance captured at its start
A background refresh's model writes are dropped by the generation guard
when the repo is switched mid-flight, but its git commands kept running
— and because the refresh read the live git instance at each step, any
command issued after the switch ran against the new repo. Now that git
commands are pinned to the directory of the instance they were built
from, capture the instance once when the refresh starts and run every
scope's git work through it, so a switch-crossing refresh keeps
addressing the repo it was started for.

The instance is captured together with the repo generation, on the UI
thread (where repo switches run), so the pair can't straddle a switch:
an old instance paired with the new generation would compute data from
the old repo and write it into the new repo's model unguarded.

This also removes the refresh workers' unsynchronized reads of the live
instance pointer, which raced its reassignment on the UI thread when a
background refresh crossed a repo switch (foreground refreshes can't
cross one: they keep Busy() true, which refuses the switch).

Two reads keyed app-state by the live instance's repo path on a worker
and now use the captured instance, fixing which repo they file under
when crossing a switch: the pull-request cache, and the "user dismissed
the base-remote prompt" flag. The base-remote menu's handlers keep
reading the live instance: a switch dismisses any open popup, so they
can't run against the wrong repo (and the OnPress body runs under a
foreground task, which blocks switching anyway).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 527124d0e0 Pin git commands to the repo they were created for
Lazygit changes the process working directory when switching repos, but
work that is still in flight for the previous repo can keep spawning
git commands after the switch — most notably a background refresh. Its
model writes are already dropped by the repo generation guard, but its
git commands would now run against the new repo. That is wasted work at
best; at worst it surfaces spurious error popups (the behind-base-
branch computation failing with "no such ref" when the old repo's main
branch doesn't exist in the new one) and pollutes caches belonging to
the old repo's reusable state (e.g. MainBranches' existing-branches
cache), which the user sees when switching back.

Give the git command builder the directory of the repo it was created
for, and pin every command it produces to that directory. The pinned
directory and the process cwd are identical until a switch happens
(NewGitCommand chdirs to the worktree path right before creating the
builder), so nothing changes in the steady state; the pin only takes
effect for commands built through a previous repo's GitCommand instance
after a switch, which now keep addressing the repo they were built for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 096a710761 Refresh pull requests through the regular refresh after picking a base remote
When the user picks a base remote in the "select remote repository"
prompt, we called setGithubPullRequests directly, bypassing the refresh
machinery — which meant hand-rolling the refresh env that call needs
(with a comment explaining why), and fetching against the branches
captured when the prompt was created. Issue a PULL_REQUESTS-scoped
refresh instead: it re-reads branches and remotes (both fast even in
large repos), fetches against those fresh values, and gets the refresh
machinery's guarantees without any special-casing. The config write is
re-read by the refresh from git config, so it is guaranteed to be
picked up.

The waiting status now covers the config write and the branches/remotes
reload, while the GitHub request itself continues as a background task
— which is how every other pull-request fetch behaves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 132f656480 Run nested-submodule commands in the parent module's directory explicitly
Deleting a nested submodule (and updating its URL) chdir'd the whole
process into the parent module, ran its git commands there, and chdir'd
back. Only those commands need to run there, and a process-wide chdir
leaks the parent module's directory into any command another goroutine
spawns during that window (e.g. a background refresh's). Set the
directory on the commands themselves instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:07:39 +02:00
Stefan Haller 4b788d5eb0
Run tests with race detection on CI (#5792)
Now that #5791 has made integration tests race-free, let's add a CI job
that runs them with race detection turned on.
2026-07-20 14:43:11 +02:00
Stefan Haller b6b5436d57 Log per-test durations during integration tests
To spot slow or anomalous tests across CI runs, record each test's run
duration when LAZYGIT_TEST_TIMING is set (to a file path);
run_integration_tests.sh prints them at the end, sorted by slowest
first. CI sets it for all integration jobs.

The harness appends to a file rather than writing to stdout/stderr
because `go test` captures those and only surfaces them with -v, which
would drown the signal in every test's verbose logs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:26:26 +02:00
Stefan Haller 03a914c04c Dump goroutine stacks when the test watchdog fires
The watchdog only log.Fatal'd with a message, so a hung test told us
that it timed out but not where it was stuck -- useless for diagnosing
an intermittent deadlock under the race detector. Dump all goroutine
stacks to stderr first (the harness surfaces this process's stderr on
failure), turning a bare timeout into an actionable stack trace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:26:25 +02:00
Stefan Haller 840a66e733 Run the integration tests under the race detector on CI
Add one extra integration-tests job that runs the whole suite under the
race detector. A `race` matrix dimension (default false) plus an include
entry adds a single git-latest job with LAZYGIT_RACE_DETECTOR set; races
live in lazygit's own Go code rather than in git, so one git version is
enough, and using latest skips the git-build steps.

The race job skips coverage collection: it's redundant with the non-race
latest job and would only slow the -race build down further.
2026-07-20 14:26:25 +02:00
Stefan Haller 334daccfab Increase integration test timeout to 30 minutes
Go's default 10-minute timeout was enough for running integration tests
normally (both locally and on CI), but with race detection turned on
they can take much longer to run. Increase the timeout unconditionally
to 30 minutes; we don't bother making a distinction between race vs.
normal, because a longer timeout doesn't hurt (I can't recall having hit
the global timeout ever; and we still have the per-test watchdog that
kills an individual test after 40s).
2026-07-20 14:26:25 +02:00
Stefan Haller 7fce58b09b Scale the integration test watchdog up under the race detector
The integration test watchdog fails a test if its recording takes longer
than 40 seconds. Under the race detector everything runs several times
slower, so legitimately slow tests (e.g. a conflicting interactive
rebase) blow that budget and fail even though nothing is actually stuck.

Key the timeout off a build-tag constant: the `race` tag is set
automatically when the binary is built with -race, so a race build gets
a 5x-longer budget while a normal build is unchanged, and the two can't
drift apart the way a runtime flag would. The base 40s stays in one
place; only the multiplier varies by build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:26:25 +02:00
Stefan Haller 46c1fa7db2 Don't let a leaked subprocess wedge the headless test runner
When lazygit exits but leaves behind a subprocess that inherited its
stderr pipe and has detached from the pty, cmd.Wait() blocks in
awaitGoroutines waiting for that pipe to reach EOF -- which never
happens while the straggler is alive. With no WaitDelay set, that wait
is unbounded, so a single leaked process hangs the whole test binary
until the 10-minute global timeout fires and panics. Worse, the timeout
discards whatever lazygit wrote to stderr before exiting (a panic, a
-race report), which is exactly the output needed to diagnose the
failure.

This surfaces under -race, where lazygit runs slow enough to widen the
window for a spawned command to still be alive when lazygit quits, and
it's a blocker for enabling the race detector on CI.

Bound the wait with cmd.WaitDelay so Wait force-closes the pipe and
returns ErrWaitDelay instead of hanging, surface the captured stderr as
the error (falling back to the wait error when nothing was printed), and
kill the child's process group on failure so a straggler can't linger
into a later test or pile up across a run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:26:25 +02:00
Stefan Haller 4390e1fb08
Fix stall with ctrl+z and fg (#5830)
When pressing ctrl+z to suspend lazygit, and then `fg` to bring it to
the foreground again, sometimes it wouldn't come to the foreground,
stalling in the background with one core using 100% CPU.

Fixes #5309.
2026-07-20 14:25:47 +02:00
Stefan Haller 94e5f570f6 Bump tcell to v3.4.1 to fix drawing on a suspended screen
The previous two commits stop gocui from flushing while suspended, but
that guard cannot be fully airtight from gocui's side: it is a
check-then-act on the suspended flag, so a flush racing the suspend
itself (e.g. from a spinner goroutine) could still reach the screen
just as it disengages, and tcell's disengageFinish mutates the cell
buffer without holding the screen lock. Upstream now closes this at
the source (gdamore/tcell#1139): draw() returns immediately on a
disengaged screen, and the draw scan loop can no longer stall on the
width-0 cells that a released cell buffer reports (#5309).

The delta over the previously pinned snapshot is these two fixes, a
CSI R input decode fix, a wasm packaging chore, and dependency bumps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:23:09 +02:00
Stefan Haller 319f43e166 Schedule a redraw when resuming from suspension
Until now the repaint after fg was accidental: it only happened because
the suspend keybinding handler still had a flush pending on the UI
thread, and only if that flush happened to run after the SIGCONT
handler had re-engaged the screen. Now that flushes are skipped while
suspended, losing that race would leave the screen blank until the next
input event arrives, so schedule a redraw explicitly (#5309).
2026-07-20 14:23:09 +02:00
Stefan Haller d887a41ad2 Don't flush the screen while suspended
When suspending with ctrl+z, the suspend keybinding handler disengages
the screen and then sends SIGSTOP to the process group, so the UI
thread freezes at the return from kill(2) with the handler's follow-up
flush still pending. When fg continues the process, that pending flush
races the SIGCONT handler's Resume. If the flush wins, Show() draws
against the disengaged screen, whose cell buffer tcell has released to
0x0 while its width/height still hold the old size; drawCell() then
reports width 0 for the out-of-range cell, the draw loop's
'x += width - 1' never advances, and the UI thread spins forever while
holding the tcell screen lock. Resume in turn blocks forever on that
lock, so the screen never re-engages and no input is ever read again:
the hard stall of #5309, only recoverable by killing the process.

Guard both flush paths with the suspended flag. For the flag to
guarantee that the screen is engaged whenever it is false, Resume must
clear it only after re-engaging (it used to clear it before); Suspend
already sets it before disengaging. This also covers the pre-existing
unsynchronized suspended check in draw(), which is subsumed by the
guards and can go.

The regression test cannot use the demonstrate-then-fix pattern: on
unfixed code the flush goroutine spins holding the screen lock, which
deadlocks any subsequent screen call including the test cleanup's
Close().
2026-07-20 14:23:09 +02:00
Stefan Haller 3ed6ce8f67 Add test showing that resuming after a suspend schedules no redraw
When lazygit is suspended with ctrl+z and brought back with fg, nothing
deliberately triggers a redraw. The screen only repaints because the UI
thread happens to have a flush pending from the suspend keybinding, and
that flush races the SIGCONT handler's resume; when it loses in the
right way, the terminal shows a blank screen until the next input event
arrives (#5309).
2026-07-20 14:23:09 +02:00
Stefan Haller b371411567 Recommend the gopls MCP tools for symbol navigation in AGENTS.md
Grep-based navigation needs manual filtering for the many colliding
method names in this codebase, while gopls answers reference and
implementation questions type-aware and exactly. Scope the guidance to
the symbol tools and keep grep for textual searches: gopls' own MCP
instructions prescribe running vulncheck at session start and
go_file_context after every file read, which costs more than it helps
here. The server is registered per user and machine, so sessions
without it must just fall back to grep rather than try to set it up.
2026-07-20 14:23:09 +02:00
Stefan Haller e91dcb7056
Suppress output from a few git commands that pollute the command log (#5834) 2026-07-19 19:29:08 +02:00
Stefan Haller 197916aafb Suppress command logs for git calls related to the ctrl+f command 2026-07-19 19:22:48 +02:00
Stefan Haller 33a2dc302e Suppress command logs for tag commands
The command log is supposed to show only commands initiated by the user;
these are commands that we run to get information for rendering, so they
pollute the log and are confusing.
2026-07-19 19:19:47 +02:00
Stefan Haller 15c83e6356
Don't block repo switching on slow network (#5829)
Fix two problems that would prevent switching repos or worktrees while a
background fetch was running, especially when the network is very slow
and the fetch takes long. See commit messages for details.

Labelling as ignore-for-release because it fixes a regression that was
introduced after the last release.
2026-07-17 17:47:27 +02:00
Stefan Haller a1561a5e69 Render the app status in a single background render loop
Each status used to start a spinner render loop of its own, running on
a worker that inherited the foreground/background flavor of the
status's owner, and exiting only once the entire status stack was
empty. That shape had a real bug: a foreground operation's loop could
be kept alive by someone else's status. Finish a quick operation with
a waiting status while a background fetch's "Fetching..." status is
still showing, and the operation's render loop — a foreground worker
task — keeps ticking until the fetch ends. Busy() stays true for that
whole time, so repo switching is refused even though nothing is in
flight anymore; with a fetch hanging on a slow network, that means
minutes. The shape was also wasteful: overlapping statuses were each
drawn by their own loop (plus a duplicate whenever a task was paused
and resumed while another status was showing), all redundantly
redrawing the same top status.

Replace the per-status loops with a single loop owned by the status
stack as a whole: whoever shows the first status starts it, and it
exits after drawing a final empty frame once the last status is
removed. The claim/release methods on StatusManager keep the loop
flag's transitions atomic with the stack under the one mutex, so a
status added while the loop is about to exit starts a fresh loop
instead of going unrendered.

The loop always runs as a background task now: rendering issues no
git commands, so it never needs to block repo switching, and a
foreground operation's busy-ness is already carried by its own worker
task. This is what fixes the bug above, and it retires the need to
thread a foreground/background flag through the waiting-status
helpers altogether.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:55:01 +02:00
Stefan Haller 7360a8459d Give the GitHub GraphQL requests a timeout
The http.Client used for fetching pull requests had no timeout, so on a
network that silently drops packets a request could stay in flight
until the OS-level TCP timeouts kick in, which can take many minutes.
The fetch has no visible status, so nothing tells the user it is still
running; bounding it keeps the refresh's worst case short, and the next
refresh simply tries again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:51:38 +02:00
Stefan Haller 5055c4fb65 Fetch GitHub pull requests as a background task
Every full refresh includes the PULL_REQUESTS scope, and the worker it
spawns inherited the refresh's foreground/background flag. Full
foreground refreshes happen at startup, after switching repos or
worktrees, and when the terminal regains focus, so the GitHub API
request ran as a foreground task there, keeping Busy() true until it
completed. On a healthy network that's a few hundred milliseconds and
nobody notices; on a very slow one the request can stall for minutes,
and every attempt to switch repos in that window was refused with
"Can't switch repositories while an operation is in progress" even
though lazygit looked completely idle. (The request has no visible
status; at most, a background fetch hanging on the same bad network was
showing its "Fetching..." spinner, pointing the blame at the wrong
operation.)

The switch-safety guard only needs to wait for operations whose
remaining git commands would run against the wrong repo after a switch.
The pull-request fetch runs no git commands at all, and its model
writes are dropped when the repo generation has changed in the
meantime, so there is no reason for it to block switching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 13:37:23 +02:00
Stefan Haller 4cf12a5b7b
Synchronize async view rendering (#5791)
Several fixes for the last concurrency problems we still had: we used to
access view properties on worker threads, which isn't allowed (writing
to a view itself is fine and guarded by a mutex, but other fields are
not, and even that mutex had a few holes that are plugged here).

With this, our integration test suite seems to be fully deterministic
and race-free, so we also remove the retry safety net (MaxAttempts=2)
that we were using to address flakiness.
2026-07-17 12:38:47 +02:00
Stefan Haller 5769ab2190 Don't retry failed integration tests
Now that we solved all known concurrency issues and our tests should be
100% deterministic, reduce MaxAttempts to 1 so that tests fail
immediately. We don't want to paper over existing flakiness any more.
2026-07-17 12:35:54 +02:00
Stefan Haller e299de3270 Assert that Model() and Context() are only accessed on the UI thread
The bounce model requires that a worker never touch UI-thread-owned
state: it should capture what it needs on the UI thread and pass that
in. Guard the two central accessors -- Model() (the git model) and
Context() (the context manager, which owns the mutable
current-context/stack) -- with a debug-only panic when they're called
off the UI thread. Since the integration tests run with -debug, a stray
worker access now fails deterministically and points at itself, rather
than surfacing later as a probabilistic data race.

One supporting change make the assertion usable: the integration test
driver inspects gui state from the test goroutine, so
GuiDriver.CurrentContext reads the context manager directly rather than
through the now-guarded c.Context().

Contexts() (the registry of context objects) is deliberately left
unguarded: workers legitimately fetch a context to grab its mutex or
check identity, so a blanket assertion there would flag safe accesses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller c23bcd6d94 Store the UI thread ID earlier 2026-07-17 12:35:54 +02:00
Stefan Haller 87ef96974e Check for exec todos on the UI thread
hasExecTodos reads Model().Commits. genericMergeCommandImpl evaluates it
when deciding whether to use a subprocess, and on the recursive auto-skip
path that runs on a worker -- so the read raced the UI thread. Bounce it
onto the UI thread there, keyed off the calledFromWorker flag the function
already carries (on the UI-thread entry path the read stays inline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 9f2886f96f Hold the file-path suggestions trie outside the model
The file-path suggestions trie is rebuilt asynchronously and then read by
the suggestions search, which runs on an AsyncHandler worker. It lived in
Model().FilesTrie, so that worker read the (UI-thread-only) model. Move
it to an atomic pointer on the SuggestionsHelper instead: it's the only
place that uses it, the helper is recreated per repo (so the cache still
resets on a repo switch), and an atomic pointer is safe to store from the
build and load from the search worker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 25a3689c01 Refresh the merge conflicts state on the UI thread
The "merge conflicts" refresh scope ran on a worker like the others, but
unlike them it does UI work rather than git work: RefreshMergeState reads
the current context and renders (or escapes) the merge-conflicts view.
Reading the context manager and rendering from a worker races the UI
thread. Bounce it onto the UI thread with onUIThreadUnlessRepoChanged,
exactly as the staging and patch-building scopes already do.

Running on the UI thread also lets EscapeMerge push the files context
directly instead of deferring the push to a separate UI task; it only
needs to drop the merge-conflicts mutex first, because the push
renders the newly focused file, which can take the mutex again. The
deferred push could lose a race against the same refresh's prompt to
continue the rebase/merge: if the prompt opened between
RefreshMergeState and the deferred push, the push declined to cover
the popup and was dropped, so closing the prompt landed the user in
the emptied merge conflicts view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 2a7b74d3f3 Don't access Model in refreshReflogCommits
This was old code that was supposed to make a race less likely, but now
that we capture model stuff on the UI thread we don't need it any more.
2026-07-17 12:35:54 +02:00
Stefan Haller d36ce51559 Capture suggestions inputs on the UI thread
RefreshSuggestions dispatched to an AsyncHandler worker that read
State.FindSuggestions and the prompt's TextArea (via GetPromptInput)
from the worker goroutine. The main thread rewrites both in
preparePromptPanel when it (re)creates a prompt panel, so an in-flight
suggestions worker races those writes -- two data races surfaced under
-race (filter_by_path/reword_commit_in_filtering_mode).

Capture both on the UI thread (RefreshSuggestions is only ever called
from UI-thread handlers) before dispatching to the worker. This is also
more correct: we search for the input as it was when dispatched, which
is what this request's AsyncHandler id corresponds to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 1b0cc02e1e Refresh once when dropping multiple stash entries
Dropping a range of stashes ran a refresh after each drop. A refresh
issued from the UI thread does its git work on a worker and applies the
model update in the background, so firing one per iteration let the
workers race: an earlier drop's refresh (which read a stash list that
still contained a later-dropped entry) could apply its result last,
leaving the stash view showing an entry that git had already removed.

Refresh once, after all the drops, so a single worker reads the final
stash list. The indices are captured up front and dropped highest-first,
so the remaining lower indices stay valid without an intervening
refresh. It's also cheaper: one `git stash list` instead of one per
entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 1efcfcc148 Don't share a live view's buffer when copying its content
moveMainContextToTop copies the current top view's content into the view
it's promoting, to avoid a flicker. The source can be a main view with a
live streaming task (e.g. resolving a conflict promotes the merge-conflicts
view over a main view that's mid-diff), and CopyContent both read and
published that source's buffer unsafely:

  - it read the source's lines/viewLines while locking only the
    destination, racing the task's concurrent Write; and
  - it aliased the source's row slices into the destination, so the
    source's ongoing appends (growslice reading the shared array) and
    refreshViewLinesIfNeeded's in-place wrapping-cache writes (&lines[i])
    kept racing this view's rendering after the copy.

Lock the source for the read, and shallow-clone the row slices so the
destination gets its own arrays. The per-row cell data is immutable once
written, so it stays shared -- the clone cost is proportional to the
number of rows, not their contents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller d48c8174d5 Refresh the patch-building panel on the UI thread
The patch-building scope ran RefreshPatchBuildingPanel directly on the
refresh worker, where it read the commit-files selection and set the patch
view's origin off the UI thread — the latter raced the UI thread's draw.
Bounce it onto the UI thread, exactly as the staging panel just above
already does, guarded on the generation so a repo switch drops it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 33b8d497c2 Guard the patch builder against concurrent access
The custom-patch git operations (move/pull/delete patch, and their rebase
continuations) run on worker goroutines and call PatchBuilder.Reset when
they've consumed the patch, clearing To and the fileInfoMap. Meanwhile the
UI thread reads that state every layout — the options bar and the mode
indicator both call Active() — so the reset raced the render.

Add a mutex. The map's entries are only ever touched on the UI thread, so
the lock only has to serialize the To field and the fileInfoMap pointer:
readers snapshot the pointer under the lock and iterate the local, and
getFileInfo drops the lock across its git diff I/O rather than holding it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 59ed1517bc Wait for the event loop to exit in integration tests
The test harness enqueued ErrQuit after a test finished, waited for the
program to go idle, then slept a fixed second and declared "gocui should
have already exited" if it hadn't. That fixed grace is fragile: under the
race detector the shutdown legitimately takes longer than a second, so
nearly every test failed with that message even though nothing was wrong.

Wait for the main loop to actually return instead. gocui now closes a
loopExited channel when MainLoop exits, and the harness blocks on it; the
existing 40s watchdog still fails a test whose loop genuinely never quits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 435e02efa8 Remove the now-dead PopupMutex
PopupMutex guarded CurrentPopupOpts against a popup being created on a
worker goroutine while the UI thread deactivated it, or reset it on a
repo switch. Now that popup and menu creation is bounced onto the UI
thread, every access to CurrentPopupOpts — create, deactivate, and the
reset-on-switch (which already runs on the UI thread) — happens on the
one goroutine, so the mutex protects nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 9754a77b64 Create popups and menus on the UI thread
Raising a popup or menu pushes a context and mutates the popup views, so
it must happen on the UI thread. But it can be triggered from a worker
goroutine — for example a WithWaitingStatus handler that hits a merge
conflict and calls PromptForConflictHandling, or a worker that shows a
confirmation — where it raced the UI thread's layout and draw code.

Bounce the creation onto the UI thread at the one point where the popup
and menu producers are injected into the popup handler, so every caller
stays oblivious to the threading. For a caller that is already on the UI
thread this adds no delay: the main loop drains the enqueued closure in
the same event-processing cycle, before it draws.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller eda2151330 Handle a command task's end-of-input on the UI thread
When a command task reaches EOF it runs onEndOfInput, which reads the
view's line height (and thus its dimensions) to decide whether to scroll,
sets the view's origin, and flushes stale cells. Reading the dimensions
and setting the origin are UI-thread-only, but this ran on the task's own
goroutine, racing the UI thread. Bounce onEndOfInput onto the UI thread,
as we already do for the new-task origin reset. It's once per render, so
it doesn't add the per-line UI-thread churn that streaming the content
would.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 65cb439076 Take the write mutex when clearing view lines and reading the buffer
A view's line buffer, its viewLines/tainted flags, and its hover state
are all written from the command-task goroutine (under writeMutex) as it
renders. But three accessors reached that same state from the UI thread
without the lock: SetView and the GUI-resize path cleared a view's lines
directly, viewsToRedrawContentOnly read the tainted flag, and Buffer read
the line buffer. Each raced a rendering task.

Guard them with writeMutex, matching the view's other buffer accessors.
These are reads/clears of state writeMutex already protects, not new
callers of it -- the view's geometry stays outside the mutex.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller f6eaed8cd4 Snapshot the view width for command-task rendering on the UI thread
A command task streams its output into a view from its own goroutine. To
track soft-wraps (so cursor-positioning escapes from a pager land on the
right line) the write path read the view's live InnerWidth, and the pty
setup read its InnerSize -- both off the UI thread, racing the UI thread
mutating the view's dimensions during layout.

Capture the width on the UI thread instead and hand it to the task: the
escape interpreter keeps a screenColMax it reads from, seeded in NewView
and refreshed per render via View.SetContentWidth (called from
newCmdTask/newPtyTask before the task's goroutine starts), and the pty
size is computed in the after-layout callback rather than in the task's
start func. The view's dimensions stay UI-thread-only; the task uses the
snapshot rather than reading them live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller e75688c101 Make the ViewBufferManager throttle flag atomic
The throttle flag is set from the goroutine that watches a task for
being stopped, and read when the next task starts up -- two different
goroutines, so the plain bool field was a data race.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 40868a9389 Hold the ViewBufferManager readLines channel in an atomic
The readLines channel, by which a running task is told to read more
lines as the user scrolls, is swapped out as tasks start and finish. It
was a plain field written from the task goroutines (when a task starts,
ends, or is replaced) and read from the UI thread in ReadLines/
ReadToEnd, so those accesses raced -- a longstanding data race (and a
plausible cause of the occasional "main view stops updating" hang, since
a torn read there could drop a scroll's read request).

Make the field an atomic.Pointer and give the running task a captured
local copy of the channel for its own send/receive, so the field itself
is only ever loaded/stored atomically. No lock is involved, so there's
nothing to untangle later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 99c1bcbf23 Reset the view origin for a new task on the UI thread
When a task renders different content to a view (a new task key), the
view's scroll origin is reset to the top via onNewKey. That ran on the
task's own goroutine, racing the UI thread, which reads the origin
(OriginY) while laying out and drawing the view -- the single largest
source of view-render data races.

Give ViewBufferManager a bounce primitive (onUIThread) that runs a
function on the UI thread and waits for it, and reset the origin through
it. This is the first use of the primitive; subsequent commits route the
rest of the task's view mutations through it too, so that the view is
only ever touched on the UI thread. It runs as background work
(OnUIThreadAndWaitBackground) so rendering doesn't count towards the app
being busy, matching how the render's gocui task is already created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller 303372d917 Perform string-task view updates on the UI thread
The closures that render static content to a main view (newStringTask
and friends) ran on the ViewBufferManager's task goroutine, calling
SetViewContent/SetOrigin/ResetViewOrigin directly on the view. Those
touch view state (the line buffer, hover cells, the origin) that the UI
thread concurrently reads and mutates while laying out and drawing, so
they raced it -- e.g. a string task's SetContent clearing the view's
lines while the UI thread's CopyContent read them, or its SetOrigin
racing the layout's OriginY read.

Bounce the whole closure onto the UI thread instead, so the view is only
touched there. The bounce blocks (OnUIThreadAndWaitBackground) so the
task still completes only once the content has actually been rendered,
which the integration-test idle detection relies on; the background
variant keeps it from counting towards the app being busy, matching the
existing treatment of view rendering as work that must not block a repo
switch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:35:54 +02:00
Stefan Haller c58aa2a23b
Remove the BLOCK_UI refresh mode (#5790)
We used to run some refreshes with a BLOCK_UI mode that causes it to run
on the main thread, blocking it. The two main reasons for doing this
were that in some cases we want the UI to update all at once, rather
then each panel when it's done refreshing (and in particular also render
a selection change in sync with the list update, e.g. when checking out
a branch, where the checked-out branch moves to the top and gets
selected there), and that we wanted to block keyboard input while the
refresh was running (so that people who like to work fast can "type
ahead"; e.g. press `e` to start an interactive rebase, and then `up` and
`d` to set the next rebase todo to dropped).

There were two problems with this: it blocked UI updates, so for
checking out a branch the status spinner would spin while the git
checkout command is running, but then freeze while the refresh was
running, which looked ugly; and also, for the "type ahead" scenario it
isn't enough to block events only while refresh runs, but we'd have to
block them during the entire operation.

This PR makes several improvements to this mechanism:
- it separates the two concerns: updating the UI all at once is a
separate RefreshOptions flag that can be set regardless of refresh mode,
and blocking keyboard input is a separate mechanism independent of
refresh. The two will often be used together, but don't have to.
- remove the special WithWaitingStatusSync mode that we used for cases
where "type ahead" support is especially important (mainly moving a
commit up/down): this used to work synchronously on the UI thread to
ensure that input is blocked, and used a special mechanism to still draw
the spinner. We don't need this any more now that we can block input
while doing the work on a background thread.
- get rid of the refresh mode (SYNC/ASYNC) altogether. We have had
separate Refresh/RefreshFromWorker calls for a while now (since #5767),
and the rule now is that all RefreshFromWorker calls are sync and all
Refresh calls are async.
2026-07-17 12:35:28 +02:00
Stefan Haller 4ff161b48d Don't wait for pull requests to be fetched in refresh
Fetching pull requests can take a long time, and we don't want to delay
the refresh by it; in particular, for a WithWaitingStatusBlockingInput
we want the UI thread to be unblocked again while pull requests are
still fetching in the background. This is similar to how we fetch the
behind values for branches in BranchLoader; this will update the UI
without much flicker when done, and doesn't have to block anything.
2026-07-17 12:32:32 +02:00
Stefan Haller 2f60280eb6 Log Refresh timing information for both sync/async
For async refreshes (from UI thread) it would only log the time it took
to schedule the refreshXxx calls, which is not useful.
2026-07-17 12:32:32 +02:00
Stefan Haller 9bb9fc8933 Run all refresh scopes on plain goroutines
The two branches of the `refresh` closure ran the scope function
identically; they differed only in that the UI-thread path registered
each scope as its own gocui task while the worker/demo path used a bare
goroutine (and only the latter logged per-scope timing).

Those per-scope tasks were redundant. performRefresh always runs under a
task that stays busy until the wg.Wait in waitAndFinalize joins every
scope goroutine: the calling worker's own task when called from a worker,
or the waitAndFinalize worker task when called from the UI thread — and
that task is created (busy) before the triggering event's task goes Done,
so there is no window in which nothing is busy. Repo-switch safety and the
integration-test idle signal are therefore already covered without giving
each scope its own task.

Collapsing to the single goroutine path also means the timing log now
fires for UI-thread refreshes too, not just worker ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller a247dfd76d Retire WithWaitingStatusSync
Nothing calls it anymore now that the commit-surgery operations run on a
worker with input blocked. Remove the helper, its bespoke synchronous
spinner loop (renderAppStatusSync/setAppStatusContent), the popup-handler
plumbing, and the interface method.

That loop was also the only thing suppressing the yellow "Rebasing" mode
indicator (and its reset button) while lazygit drives a rebase itself.
Move that suppression to WithWaitingStatusBlockingInput so it applies to
every input-blocking commit-surgery op — including the ones that already
ran on a worker (edit, drop, and so on) and previously let the indicator
flash on mid-operation. It's cleared after the refresh, so an operation
that legitimately leaves a rebase in progress still shows the mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller a324f8aef1 Drop the now-unused UI-thread CheckMergeOrRebase path
With the last synchronous commit-surgery callers moved to workers,
nothing runs CheckMergeOrRebase on the UI thread anymore, so
CheckMergeOrRebaseWithRefreshOptionsFromUIThread has no callers. Remove
it and fold the shared checkMergeOrRebaseImpl back into
CheckMergeOrRebaseWithRefreshOptions, which is now always on a worker.
The runAction closure loses its calledFromWorker parameter for the same
reason.

genericMergeCommandImpl keeps its calledFromWorker flag: the
merge/rebase-continue subprocess path still runs on the UI thread when
invoked straight from the menu, and on a worker for the recursive
auto-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller d802cbdddf Block input during the worker commit-surgery ops
edit, quick-start rebase, drop, reword, squash, fixup, amend
(including the amend-attribute author operations) and
discard-file-from-commit all run a rebase on a worker. A key pressed
while one is in flight could act on a stale commit or todo — pressing e
to start an interactive rebase, then up+d before it finishes, is the
motivating example. Switch them from WithWaitingStatus to
WithWaitingStatusBlockingInput so input is held and replayed against the
post-operation state, matching the commit-surgery ops that were already
sync.

Left alone: the custom-patch move/delete/pull-into-commit rebases (no
need to block input while building and applying a patch), the
loading-more-commits and patch-building toggle spinners (no rebase to
disrupt), and fetches and other non-surgery operations where blocking
navigation would only get in the way.
2026-07-17 12:32:32 +02:00
Stefan Haller 352883c52b Run the sync commit-surgery ops on a worker with input blocked
Move, revert, squash-fixups, create-fixup and cherry-pick paste ran
their rebase synchronously on the UI thread via WithWaitingStatusSync,
which froze the UI for the duration but kept the user from disrupting the
operation with a stray keypress. Switch them to
WithWaitingStatusBlockingInput so the git work runs on a worker — the UI
keeps rendering and the spinner animates — while input stays blocked for
the whole operation, as before.

discard-patch-from-commit also moves off WithWaitingStatusSync, but as a
plain WithWaitingStatus: it's a custom-patch command, and those don't
block input.

The bodies now follow the worker conventions: model state they need is
captured on the UI thread before dispatching, self.c.Refresh becomes
RefreshFromWorker, and CheckMergeOrRebase uses the worker variant. An
operation that moves the selection does so in the refresh's Then, so it
lands in the same frame as the refreshed commit list; squash sets it as
an absolute index there, because the shorter list would clamp a relative
move.
2026-07-17 12:32:32 +02:00
Stefan Haller 62098ca603 Pass captured state to moveFixupCommitToOwnerStackedBranch
It reads the selected index and the commits and branches models to
decide where to move the fixup commit. Take those as parameters,
captured on the UI thread by the callers, so the function can run its
rebase on a worker without reading the model there. No behavior change;
the callers still run on the UI thread for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 707b04a8c2 Add a WithWaitingStatusBlockingInput helper
Bracket gocui's BeginBlockingEvents/EndBlockingEvents around a
worker operation that shows a waiting status. The block is begun
synchronously on the UI thread, before the operation is dispatched to a
worker, so no keypress can slip through in between; it ends via
OnUIThread once the operation and its refresh have applied their UI
updates, so the replayed keys act on the refreshed state.

This composes what the retiring WithWaitingStatusSync did — show a
status and block input — but on a worker, so the UI keeps rendering
(spinner animates, model updates land) instead of freezing. Callers
follow in subsequent commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 6893d9a759 Add gocui primitives to block input during an operation
Long-running operations that lazygit drives itself (rebases, and the
commit surgery built on them) can be corrupted by keys the user presses
while they run: pressing e to start an interactive rebase, then up+d
before it finishes, must act on the resulting todo list, not race the
rebase. WithWaitingStatusSync gets this today only as a side effect of
freezing the UI thread, which the rest of this branch is moving away
from.

Add a nestable counter, BeginBlockingEvents/EndBlockingEvents, that
withholds input at the event-dispatch layer without freezing anything:
while blocked, key events are buffered and replayed in order once the
count returns to zero (so they act on the now-current context), mouse
clicks and hover are dropped (replaying them against a changed layout
would target the wrong thing), and scrolling, resize, focus and all
rendering keep flowing. These are the reusable core; a gui-level helper
that brackets them around a worker operation follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 88811e6795 Remove the RefreshMode field
With sync vs async now derived from the calling thread, the Mode field
and its SYNC/ASYNC constants no longer carry any information: Refresh is
always async, RefreshFromWorker always sync. Drop the field, the type,
and the Mode argument at every call site, and reduce the debug log's
mode name to a plain sync/async derived from calledFromWorker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 8580c78cc0 Derive sync vs async refresh from the calling thread
Whether a refresh should block or run in the background was controlled
by the Mode field, but that always lined up with the calling thread: a
UI-thread Refresh must not block the UI, while a RefreshFromWorker runs
on a worker where blocking is exactly what we want. Now that Then and
BatchUIUpdates work regardless of that choice, drop Mode from the
decision and key it off calledFromWorker instead:

  - Refresh (UI thread) runs its scopes and the finishing step (wait,
    batch flush, Then) on workers, so the caller returns immediately —
    what ASYNC used to mean.
  - RefreshFromWorker runs them on the calling worker, blocking it until
    everything is done — what SYNC used to mean.

Demos keep taking the blocking, inline path so everything still lands in
one deterministic frame.

In practice this flips the handful of RefreshFromWorker calls that
passed ASYNC — they now block their worker until the refresh finishes,
keeping the waiting-status spinner up until the UI actually updates —
and the many UI-thread refreshes that defaulted to SYNC, which no longer
freeze the UI thread while the git work runs. Mode now only feeds the
log line; the next commit removes it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 63bd2d98c0 Show a waiting status while creating a branch
Creating a branch checks it out, and checking out a distant ref (a tag
or a commit far from HEAD) can take a noticeable while. NewBranch ran
that synchronously in the prompt's confirm handler, on the UI thread, so
the UI froze — no spinner, no repaint — until it finished.

Move the branch creation (and the autostash path) onto a worker with a
waiting status, mirroring CheckoutRef, and refresh from the worker so
the UI thread stays live and the spinner keeps animating.

Push the branches context from the refresh's Then rather than up front:
the refresh already batches its UI updates, so switching panels there
lands the switch in the same frame as the refreshed branch list instead
of flashing the pre-refresh list while the checkout is still running.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller bfd3b7b47e Allow Then and BatchUIUpdates to work with an async refresh
Then, and BatchUIUpdates, previously only worked for a SYNC refresh: the
calling goroutine blocked in wg.Wait until every scope had finished, and
only then flushed the batch and ran Then. An ASYNC refresh had no such
join point — it dispatched each scope onto its own worker and returned
right away — so Then was forbidden (it would have run before the scopes
finished) and a batch would never be drained.

Give the async path a join of its own. Both paths now register their
scopes in the WaitGroup, and the finishing work — wg.Wait, the batch
flush, and Then — moves into a closure. A SYNC refresh runs it inline as
before; an ASYNC refresh dispatches it to a worker, so the caller still
returns immediately but the batch and Then run once every scope is done.

Besides lifting the restriction, this makes SYNC and ASYNC differ only
in whether the finishing work blocks the caller, which is what lets a
later commit drop the mode entirely and key the choice off the calling
thread instead.
2026-07-17 12:32:32 +02:00
Stefan Haller f319522d5b Remove fRunsOnUIThread variable; use calledFromWorker directly
There is no f() function any more, so a variable named "f runs on"
doesn't make sense. And we also don't need it any more; it used to be
necessary when its meaning was not exactly the same as
`!calledFromWorker`, but also included the BLOCK_UI case, but that has
changed several commits ago.
2026-07-17 12:32:32 +02:00
Stefan Haller d70d70aad2 Get rid of pointless f() indirection
This was useful when there was a BLOCK_UI mode where f() was called
differently, but now we no longer need it. I'm making this change as a
separate commit because folding it into the previous one (which would
conceptually have made sense) would have made that diff unreadable
because of the indentation change.

The variable `fRunsOnUIThread` and its comment no longer make sense now;
we'll clean this up next.

The diff is best viewed with --ignore-all-space.
2026-07-17 12:32:32 +02:00
Stefan Haller 4acfc88065 Replace the BLOCK_UI refresh mode with a BatchUIUpdates flag
BLOCK_UI ran the whole refresh on the UI thread and parked it in a
wg.Wait for the duration, so the UI (and its spinner) froze while the
git work ran. Blocking the UI was never the point — the point was to
apply all the scopes' updates in one frame instead of a per-scope
cascade — and if we genuinely wanted to block input it should span the
whole operation, not just its refresh, which needs a gocui-level
mechanism we don't have.

So drop the mode and add a BatchUIUpdates option that achieves the
"one frame" effect without blocking: each scope's UI-thread bounce is
collected into a shared refreshBounceBatch during the refresh, and once
every scope has finished they're all applied inside a single OnUIThread
task. gocui drains every queued event before it redraws, so one task
means one repaint. The refresh itself now runs SYNC — on a worker when
issued from one (checkout, move-to-new-branch, the rebase-edit result
handling), so the UI thread stays live and the spinner keeps animating.

The batch needs a mutex because the scopes add concurrently from their
worker goroutines, and a closed flag so that any bounces enqueued after
the flush starts — the nested ones a flushed bounce produces in turn,
e.g. scrolling the selection into view — are dispatched immediately as
ordinary follow-ups rather than collected into a batch that nothing
will drain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller 504e5b3f74 Remove the error return value from the onUIThreadUnlessRepoChanged lambda
All clients pass a function that returns nil.
2026-07-17 12:32:32 +02:00
Stefan Haller 36f193a2e8 Remove return value from PromptToContinueRebase
It always returned nil.
2026-07-17 12:32:32 +02:00
Stefan Haller 2765147b71 Note in AGENTS.md that gocui lives in-tree
Agents (and humans new to the repo) repeatedly go looking for the gocui
sources in go.mod, go.sum, or the module cache and hit a dead end, because
gocui is a fork maintained in-tree under pkg/gocui rather than pulled in as
a dependency. Record that in AGENTS.md so the dead end is avoided.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:32:32 +02:00
Stefan Haller edec427746
Fix command log streaming race (#5789)
Serialize concurrent writes to the streamed command's output writer.
`runAndStreamAux` funnels a command's stdout and stderr into a single
`cmdWriter` (the command-log panel, or a buffer when output is
suppressed) from two separate goroutines: stderr through the MultiWriter
set on `cmd.Stderr`, and stdout through the `onRun` callback. Those
goroutines
wrote the shared writer without any synchronization, racing on the
prefixWriter's `prefixWritten` flag and interleaving the two streams.
Wrap the writer so its writes are serialized.
2026-07-17 12:32:00 +02:00
Stefan Haller a61be44e92 Wait for the streamed command's output goroutine before reading its buffers
runAndStreamAux reads the stdout buffer (and, when output is suppressed,
the combinedOutput buffer) for its error message after handler.wait()
returns, but the goroutine that fills those buffers by draining the
command's output isn't awaited, so the reads raced its final writes.

Own the goroutine here rather than letting the onRun callbacks spawn it,
and join it before reading the buffers. The pty reader reaches EOF on its
own once the process exits, but the non-pty pipe never does, so its
handler now closes the read end to unblock the reader; the pipe is
synchronous, so by the time the command has exited all of its output has
already been read and nothing is lost. This also plugs the goroutine that
the non-pty streaming path previously leaked on every command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:19:14 +02:00
Stefan Haller d097519c05 Serialize concurrent writes to the streamed command's output writer
runAndStreamAux funnels a command's stdout and stderr into a single
cmdWriter (the command-log panel, or a buffer when output is suppressed)
from two separate goroutines: stderr through the MultiWriter set on
cmd.Stderr, and stdout through the onRun callback. Those goroutines
wrote the shared writer without any synchronization, racing on the
prefixWriter's prefixWritten flag and interleaving the two streams.
Wrap the writer so its writes are serialized.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:19:14 +02:00
Stefan Haller 4b97c2ba61
Fix side panel rendering when branches/commits are not their panel's first tab (#5825)
Side panel rendering was broken when 'branches' or 'commits' were not
the first tab in their respective side panel.

Fixes #5823.
2026-07-17 12:16:24 +02:00
Stefan Haller 74a77e58be Assign the transient contexts' initial windows from the side panel config
The transient contexts (remoteBranches, subCommits, commitFiles) take
over the window of the context they are drilled into from, but until
then they carry a hardcoded initial window ("branches" or
"commits"). Under a gui.sidePanels config where those tabs aren't
their panel's first, no window of that name exists, leaving the
window-to-view map with entries for windows the layout never
produces. The previous commit made such entries harmless, but there's
no reason to have contexts point at nonexistent windows in the first
place; assign them the window hosting branches or commits instead,
which the config validation guarantees to exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:07:24 +02:00
Stefan Haller bf4f5827e7 Don't show a transient view whose window is not part of the layout
With gui.sidePanels, a panel's gocui window is named after its first
tab, so when branches is grouped behind, say, worktrees, there is no
window called "branches" at all. The transient contexts
(remoteBranches, subCommits, commitFiles) initially point at the
windows "branches" and "commits", and layout() showed their views
whenever the window-to-view map named them as their window's current
view — without checking that the window exists in the layout. Since
the map is seeded from the contexts themselves, a window that no
panel owns keeps naming a transient view as its current view, and
that view had just been parked at full screen size (the fallback for
views in unlaid-out windows), so it covered every side panel below it
in z-order.

Only show a transient view if its window actually received dimensions
in this layout.

Fixes #5823.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:07:24 +02:00
Stefan Haller 38e1fe0493 Add tests showing ghost views when branches/commits are not their panel's first tab
With gui.sidePanels, a panel's gocui window is named after its first
tab. The transient contexts (remoteBranches, subCommits, commitFiles)
initially point at the windows "branches" and "commits"; when the
config gives no panel that name, their views end up visible at full
screen size, covering every side panel below them in z-order (issue
#5823).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:07:24 +02:00
Stefan Haller 0162839f0c
Bump tcell to an unreleased snapshot to fix a shutdown race (#5824)
tcell's filterEvents goroutine sends events into eventQ with a plain
blocking send, while Fini (via finish/finalize) closes eventQ after
closing the quit channel. The goroutine can have already committed to
the ev = <-inQ select arm when quit is closed, so its send into eventQ
races with the close; the race detector flags this (send and close on
the same channel are unsynchronized), and if the close wins, the send
panics with "send on closed channel".

This was caught by the integration tests under the race detector, where
every test drives a real tScreen over a MockTerm and tears it down via
Fini, but it equally affects real-terminal shutdown.

Upstream fixed it in 243630d2 ("Fix screen Init/Fini races") by tracking
the filter goroutine in a WaitGroup that finalize waits for before
closing eventQ, and guarding the send with a select on quit. That commit
is not in a tagged release yet (latest is v3.4.0), so pin the
pseudo-version; the delta over v3.4.0 is just this fix, a Windows
key-release fix, a cell-rendering perf tweak, and dependency bumps.
2026-07-17 08:49:20 +02:00
Stefan Haller 14d717d77f Bump tcell to an unreleased snapshot to fix a shutdown race
tcell's filterEvents goroutine sends events into eventQ with a plain
blocking send, while Fini (via finish/finalize) closes eventQ after
closing the quit channel. The goroutine can have already committed to
the ev = <-inQ select arm when quit is closed, so its send into eventQ
races with the close; the race detector flags this (send and close on
the same channel are unsynchronized), and if the close wins, the send
panics with "send on closed channel".

This was caught by the integration tests under the race detector,
where every test drives a real tScreen over a MockTerm and tears it
down via Fini, but it equally affects real-terminal shutdown.

Upstream fixed it in 243630d2 ("Fix screen Init/Fini races") by
tracking the filter goroutine in a WaitGroup that finalize waits for
before closing eventQ, and guarding the send with a select on quit.
That commit is not in a tagged release yet (latest is v3.4.0), so pin
the pseudo-version; the delta over v3.4.0 is just this fix, a Windows
key-release fix, a cell-rendering perf tweak, and dependency bumps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 08:41:25 +02:00
Stefan Haller 07745afc57
Escape the merge conflicts view before prompting to continue the rebase (#5822)
When the last conflict of a file is resolved, a files refresh both
offers to continue the rebase/merge (if we started it ourselves) and,
via its merge-conflicts scope, escapes from the merge conflicts view
back to the files context. The two race: the prompt is bounced onto the
UI thread by the files worker, while the escape's context push is queued
separately by EscapeMerge, and it deliberately refuses to push the files
context over a popup. So if the prompt opens first, the escape does
nothing, and closing the prompt lands the user in the stale merge
conflicts view — usually already emptied by the escape's state reset —
instead of the files panel. No later refresh rescues this.

Fix this by escaping from the merge conflicts view right before opening
the prompt. This runs on the UI thread and doesn't hold the merge
conflicts mutex, so it can reset the state and push the files context
synchronously; whichever side runs first, the prompt now always opens on
top of the files context, and EscapeMerge's guarded push still does
nothing only when that's the right thing to do.

This is a timing race with no deterministic regression test; it showed
up as a rare flake in tests that cancel the continue prompt (e.g.
commit/amend_when_there_are_conflicts_and_continue) when looping the
integration tests under the race detector.
2026-07-16 14:44:20 +02:00
Stefan Haller d786c9d79b Escape the merge conflicts view before prompting to continue the rebase
When the last conflict of a file is resolved, a files refresh both
offers to continue the rebase/merge (if we started it ourselves) and,
via its merge-conflicts scope, escapes from the merge conflicts view
back to the files context. The two race: the prompt is bounced onto
the UI thread by the files worker, while the escape's context push is
queued separately by EscapeMerge, and it deliberately refuses to push
the files context over a popup. So if the prompt opens first, the
escape does nothing, and closing the prompt lands the user in the
stale merge conflicts view — usually already emptied by the escape's
state reset — instead of the files panel. No later refresh rescues
this.

Fix this by escaping from the merge conflicts view right before
opening the prompt. This runs on the UI thread and doesn't hold the
merge conflicts mutex, so it can reset the state and push the files
context synchronously; whichever side runs first, the prompt now
always opens on top of the files context, and EscapeMerge's guarded
push still does nothing only when that's the right thing to do.

This is a timing race with no deterministic regression test; it
showed up as a rare flake in tests that cancel the continue prompt
(e.g. commit/amend_when_there_are_conflicts_and_continue) when
looping the integration tests under the race detector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 09:11:16 +02:00
Stefan Haller 080da5cacf
Fix idle notification deadlock (#5821)
Running the integration tests in a loop under the race detector
eventually hung in demo/bisect. The goroutine dump shows the cycle: a
background worker's task.Done() held the task manager's mutex while
blocking on the unbuffered idle-listener channel send, and the test
runner goroutine — the only reader of that channel — was itself blocked
in NewTask on that same mutex, on its way to enqueueing a caption render
(SetCaption -> Render -> OnUIThread). Neither side could proceed: the
notification couldn't be delivered until the test goroutine got the
mutex, and the mutex couldn't be released until the notification was
delivered.

The root problem is that the busy-to-idle notification is a blocking
rendezvous performed while holding the mutex, so it needs the waiter's
cooperation at a moment where the waiter may legitimately need the mutex
first.

Make the notification fire-and-forget instead: WaitUntilIdle waits on a
condition variable and re-checks "is any task busy?" under the mutex,
and the busy-to-idle transition broadcasts, which never blocks. Waiting
is now level-triggered rather than edge-triggered, which is also more
robust: a wait can no longer be satisfied by a stale idle transition
produced by an unrelated background task, because the predicate is
evaluated against the current state. This relies on the previous commit
having made replayed input events carry their task from submission;
without that, the wait could return in the window where an event is in
flight but not yet picked up by the main loop.
2026-07-15 15:05:10 +02:00
Stefan Haller 0ce857c717 Fix a deadlock between task.Done() and the integration test's idle wait
Running the integration tests in a loop under the race detector
eventually hung in demo/bisect. The goroutine dump shows the cycle: a
background worker's task.Done() held the task manager's mutex while
blocking on the unbuffered idle-listener channel send, and the test
runner goroutine — the only reader of that channel — was itself blocked
in NewTask on that same mutex, on its way to enqueueing a caption
render (SetCaption -> Render -> OnUIThread). Neither side could
proceed: the notification couldn't be delivered until the test
goroutine got the mutex, and the mutex couldn't be released until the
notification was delivered.

The root problem is that the busy-to-idle notification is a blocking
rendezvous performed while holding the mutex, so it needs the waiter's
cooperation at a moment where the waiter may legitimately need the
mutex first.

Make the notification fire-and-forget instead: WaitUntilIdle waits on a
condition variable and re-checks "is any task busy?" under the mutex,
and the busy-to-idle transition broadcasts, which never blocks. Waiting
is now level-triggered rather than edge-triggered, which is also more
robust: a wait can no longer be satisfied by a stale idle transition
produced by an unrelated background task, because the predicate is
evaluated against the current state. This relies on the previous commit
having made replayed input events carry their task from submission;
without that, the wait could return in the window where an event is in
flight but not yet picked up by the main loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:01:04 +02:00
Stefan Haller 664a65d584 Track replayed test input as busy from the moment it is submitted
Integration tests synchronize with lazygit through the task manager:
after submitting an input event, the test driver waits until the
program goes idle before asserting. But a submitted event only got its
task once the main loop picked it up from the events channel; while it
was still in flight (handed to the poller goroutine, or sitting in the
channel), no task existed for it, so the program could look idle even
though input was still pending.

The edge-triggered idle protocol mostly papers over this: each wait is
satisfied by the *next* busy-to-idle transition, which in practice is
the one produced by processing the submitted event. It only goes wrong
when some other task (e.g. a background refresh) completes in that
window, producing an edge the waiting test mistakes for its own — a
rare source of test flakes. The next commit replaces that protocol
with a level-triggered one, for which the window would be fatal rather
than rare: a wait falling into the gap would return immediately.

Close the gap by creating the task on the test goroutine before the
event is submitted, and carrying it through the poller into the main
loop, which uses it instead of creating its own. The new Replay*
methods own this invariant, and the replayed-events channels are no
longer exported, so tests can't submit an untracked event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:01:04 +02:00
Stefan Haller 7e1073a0ee Extract the tcell-to-gocui event conversion out of pollEvent
A following commit needs pollEvent to attach information from the
replayed-event wrappers to the GocuiEvent it returns. With the
conversion inlined there is no seam to do that in, because every branch
of the type switch returns directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:01:04 +02:00
Stefan Haller 4fea011021
Merge v0.63.1 to master (#5820) 2026-07-15 14:15:01 +02:00
Stefan Haller 733c1a487f Merge v0.63.1 into master
Resolve the pkg/gocui/gui.go conflict by keeping master's background-task
structure (Update/update(background), taskManager) and applying the
unbounded user-event queue on top — the same end state as if the fix had
been written on master directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:08:59 +02:00
Stefan Haller aafe61082e
Allow releasing from a branch other than master (#5819)
This is useful for cutting a patch release for the previous version when
master already contains work that shouldn't be released yet.

Scheduled runs are unaffected: with no input provided, the ref is empty
and the checkout falls back to the default branch.
2026-07-15 13:32:02 +02:00
Stefan Haller bd8c06ddc0 Rename version_bump options to be extra clear
I keep getting slightly confused as to which is which, so make this
extra clear.

While at it, change the default to minor, this is the option that is
more often used now that we don't have regular scheduled releases any
more.
2026-07-15 13:23:11 +02:00
Stefan Haller 1d99ba56fc Allow releasing from a branch other than master
This is useful for cutting a patch release for the previous version
when master already contains work that shouldn't be released yet; for
example, v0.63.1 had to be tagged and released by hand from a v0.63.1
branch off the v0.63.0 tag because the workflow could only release
master.

Scheduled runs are unaffected: with no input provided, the ref is
empty and the checkout falls back to the default branch.
2026-07-15 13:23:11 +02:00
Stefan Haller dda0af0f48 Allow having branch and tag with the same name
When creating a patch release from a branch called `v0.63.1`, the new
tag would get the same name and pushing it would fail with `error: src
refspec v0.63.1 matches more than one`.
2026-07-15 13:23:11 +02:00
Stefan Haller a65d468cd3 Determine the latest tag from the checked-out commit's history
The Get Latest Tag step used to pick the most recently created tag in
the entire repo, regardless of whether it is reachable from the commit
being released. In preparation for supporting releases from branches
other than master, use the nearest tag that is an ancestor of the
checked-out commit instead. This way, a patch release cut from an
older release branch bumps that branch's own latest tag even when
master already carries a newer release, and the "changes since last
release" check compares against the release that actually precedes
this one in history.
2026-07-15 10:56:56 +02:00
Stefan Haller 4c78076730 Fix a deadlock on Windows when switching between longer diffs (#5815)
The Windows PTY support that was newly introduced in v0.63.0 had a
potential deadlock problem: when switching between longer diffs, lazygit
could lock up. This should hopefully be fixed with this PR.
2026-07-15 10:32:19 +02:00
Stefan Haller f116874f0a Fix a deadlock when a Windows pty task is stopped mid-output
winPty.Close could block indefinitely, and it is called while holding
the global PtyMutex and while the task's onDone sync.Once is
executing, so blocking there wedges the task's entire cleanup chain:
the next NewTask call blocks on <-notifyStopped while holding
waitingMutex, every later task for that view queues up behind it, and
onResize blocks on PtyMutex — a full UI freeze. (Reported by a user
via go-deadlock's 30s watchdog; a regression from the ConPTY support
introduced for v0.63.0.)

ClosePseudoConsole is what blocks; before Windows 11 24H2 it can do
so in two ways. It flushes the client's pending output into the out
pipe, but a stopped task's scanner goroutine has already quit
draining, so with a client that's still producing output the flush
never completes; this can also wedge the background waiter's
closeHpc, which runs with the pipes deliberately left open. And it
waits for the console host to exit, but closing only delivers
CTRL_CLOSE_EVENT to the attached client without terminating it, so a
client that keeps running (git still computing an expensive diff, a
pager waiting for input) keeps the host alive arbitrarily long.

Run the teardown on a background goroutine so Close returns
immediately no matter which of these strikes, and within it close our
pipe ends before the pseudoconsole, without taking p.mu: breaking the
pipes fails a pending flush fast, which also unblocks a waiter
already stuck in one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:31:11 +02:00
Stefan Haller c2489e1c13
Fix userEvents panic (#5793)
In #5756 we changed the userEvents channel to a fixed 256-slot channel
with a non-blocking send that panicked when the channel was full. It
turns out that this panic can happen in real use:

- Toggling a directory of several hundred files into a custom patch
(reliably): the operation runs on a worker behind a waiting status,
whose spinner enqueues a content-only render on every tick, and over the
long operation these outrun the UI loop and overflow the buffer.
- Editing the config in an editor that suspends lazygit: the editor
subprocess runs on the UI thread, so the loop drains nothing for the
whole editing session, and the full refresh fired on resume fans out
across every scope at once — a burst of updates that overflows before
the just-resumed loop catches up.
- Any time the UI thread blocks for a long time, the periodic refreshes
keep enqueuing and eventually overflow.

The 256-slot buffer was chosen deliberately, with the panic as a "should
never happen" guard, to preserve two properties: FIFO ordering of
same-goroutine Update calls (an earlier goroutine-per-Update design
reordered them), and no self-deadlock (a blocking send from the UI
thread would block against the loop that drains it). But a fixed channel
can only offer those by crashing on overflow.

Replace it with an unbounded, order-preserving queue: a mutex-guarded
slice plus a buffered(1) doorbell channel that wakes the main loop's
select. Enqueuing appends and rings the doorbell; the loop drains the
slice to empty on each wake. This keeps FIFO order and never blocks the
caller, so there is no self-deadlock and no overflow to panic on — under
a stall the queue just grows and then drains.

Fixes #5772.
2026-07-15 10:18:51 +02:00
Stefan Haller f0b139f3ab Log the user-event queue's high-water mark
Now that the queue is unbounded, its depth is a useful signal for
understanding how the event loop behaves under load — and we expect it
to look very different across builds (e.g. master, which carries the
bounce-state-updates-to-ui-thread work, versus the v0.63.0 release this
fix ships in). Track the deepest the queue has ever been and log an Info
line whenever that record is broken, so the numbers show up in the log
for later reasoning. The mark is session-wide and doesn't reset when the
queue drains.

gocui has no logger of its own, so it exposes the new depth through a
handler (matching the existing SetFocusHandler / SetOpenHyperlinkFunc
pattern) that the gui registers to log via its own logger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:14:05 +02:00
Stefan Haller 49eefbcf37 Make the user-event queue unbounded
Update and friends enqueued onto a fixed 256-slot channel with a
non-blocking send that panicked when the channel was full. That guard
was firing in real use:

 - Toggling a directory of several hundred files into a custom patch
   (reliably): the operation runs on a worker behind a waiting status,
   whose spinner enqueues a content-only render on every tick, and over
   the long operation these outrun the UI loop and overflow the buffer.
 - Editing the config in an editor that suspends lazygit: the editor
   subprocess runs on the UI thread, so the loop drains nothing for the
   whole editing session, and the full refresh fired on resume fans out
   across every scope at once — a burst of updates that overflows before
   the just-resumed loop catches up.
 - Any time the UI thread blocks for a long time, the periodic refreshes
   keep enqueuing and eventually overflow.

The 256-slot buffer was chosen deliberately, with the panic as a
"should never happen" guard, to preserve two properties: FIFO ordering
of same-goroutine Update calls (an earlier goroutine-per-Update design
reordered them), and no self-deadlock (a blocking send from the UI
thread would block against the loop that drains it). But a fixed
channel can only offer those by crashing on overflow.

Replace it with an unbounded, order-preserving queue: a mutex-guarded
slice plus a buffered(1) doorbell channel that wakes the main loop's
select. Enqueuing appends and rings the doorbell; the loop drains the
slice to empty on each wake. This keeps FIFO order and never blocks the
caller, so there is no self-deadlock and no overflow to panic on — under
a stall the queue just grows and then drains.

This also removes an inconsistency: updateContentOnly did a plain
blocking send while update panicked, so the two paths disagreed on what
happened when the queue was full. Both now share the same enqueue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:14:05 +02:00
Stefan Haller 9f51f044fa
Improve index.lock retry mechanism (#5788)
In v0.63.0 we made a change to no longer use `GIT_OPTIONAL_LOCKS=0` on
git commands that are part of a "foreground" refresh, meaning the
refresh after a lazygit command or the focus-in refresh. We do this on
purpose to keep git's mod date cache from becoming stale, which could
make lazygit become slower over time. However, this caused a problem for
users who work very fast: staging a file and then immediately pressing
shift-A to amend while the staging's refresh is still running would show
the dreaded index.lock error.

We already had a retry-on-index-lock-error mechanism in place, but it
wasn't used for commands like amend or commit; fix this so that the
retry loop works for these too, and also make the retry window a little
longer, and fix a problem where it wouldn't work in linked worktrees or
submodules.

Closes #5778.
2026-07-15 10:12:51 +02:00
Stefan Haller 4052057eee Back off exponentially between lock-error retries
The retry budget was five fixed 50ms waits (250ms total). A foreground
`git status` refresh can hold index.lock for longer than that on a large
repo, so the retries could be exhausted before the lock clears. Wait 20ms
before the first retry and double each time, giving seven attempts over a
bit more than a second — enough to outlast a slow refresh while keeping
the common case (a lock that clears almost immediately) fast. The initial
delay is now a runner field so tests can zero it out instead of sleeping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:44 +02:00
Stefan Haller e3ecb77939 Recognize index.lock contention in worktrees and submodules
The retry check matched the literal ".git/index.lock", which only ever
appears for the main worktree. A linked worktree's lock is at
.git/worktrees/<name>/index.lock and a submodule's is under its own git
dir, so contention there was never retried. Match the bare "index.lock"
fragment instead, which covers all of them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:44 +02:00
Stefan Haller c1cd500fa7 Retry lock errors reported only through the command's error
Have isRetryableError also inspect the returned error, not just the
captured output. Streamed commands (amend, commit, and other operations
run through the gpg helper) don't capture output, so their index.lock
failures were slipping past the retry loop and surfacing to the user as
a hard "Git command failed". Now they retry like every other command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:44 +02:00
Stefan Haller 0902c5c058 Demonstrate that a lock error in a streamed command isn't retried
The gpg helper runs commands like amend with StreamOutput, so their
output isn't captured and a failed run returns an empty output string;
the index.lock message is carried by the error instead. isRetryableError
only inspects the output, so the retry loop never fires for these
commands. In practice this means a `shift-A` amend issued while a
foreground `git status` refresh briefly holds index.lock fails outright
instead of retrying.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:44 +02:00
Stefan Haller e90daaf812 Unify the git command lock-retry loops
RunWithOutput and RunWithOutputs each carried their own near-identical
copy of the index.lock retry loop. Extract the loop into a single
retryOnLockError helper so the retry policy lives in one place, ahead of
changing that policy. Behavior is unchanged; the added tests characterize
it (success and non-lock errors run once, a lock error in the output is
retried).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:08:44 +02:00
Stefan Haller bb2d6e8bbd
Clarify contribution policy (#5809) 2026-07-14 14:58:32 +02:00
Stefan Haller 50122e6886 Don't invite for contributions at startup 2026-07-14 14:54:56 +02:00
Stefan Haller 76ad5a3552 Clarify the contribution policy 2026-07-14 14:54:40 +02:00
Stefan Haller bea025f5b7
Fix potential deadlock when switching repos (#5797)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
See commit messages for details.

Labelling as ignore-for-release because it fixes a regression that was
introduced since the last release.
2026-07-10 17:22:21 +02:00
Stefan Haller 3a0ba6bf4d Fix data race on the triggerFetch field
startBackgroundFetch assigned the field from its own goroutine, and
only after the initial fetch had completed, while the UI thread reads
it in triggerImmediateFetch on every repo switch, with no
synchronization.

Create the channel in startBackgroundRoutines instead, which runs on
the UI thread before the fetch goroutine is spawned; everything the UI
thread does afterwards is ordered after the write, so the read is
race-free without any locking. To make this possible, goEvery now
takes the retrigger channel as a parameter instead of creating and
returning it; callers that have no use for a retrigger channel pass
nil, and a nil channel in a select is simply never ready.

As a side effect, a repo switch that happens before the fetch loop has
started (during the intro popup or the initial fetch) now latches a
trigger and causes an immediate fetch once the loop is running, where
previously it was silently dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:16:46 +02:00
Stefan Haller 58e121b933 Don't block the UI thread when triggering an immediate fetch on repo switch
Switching repos triggers an immediate background fetch by sending on
the goEvery retrigger channel. The send was blocking, but the goEvery
loop only receives between callbacks: while a fetch is in flight, it
waits for that fetch to finish before returning to its select. So a
repo switch that landed while a fetch was in flight would stall the UI
thread for the remainder of the fetch.

Worse, since worker refreshes capture state on the UI thread with a
blocking OnUIThreadAndWaitBackground call, the in-flight fetch's
post-fetch refresh can itself be waiting for the UI thread, turning
that stall into a deadlock cycle:

  UI thread: switchTo -> triggerImmediateFetch, blocking send
  goEvery loop: waiting for the in-flight fetch to finish
  fetch worker: PostFetchRefresh -> RefreshFromWorker, waiting for
    the UI thread

Make the send non-blocking, and give the channel a buffer of one so
that a trigger arriving while a fetch is in flight is latched rather
than dropped; that fetch is fetching the previous repo, so we still
need another one after it. The goEvery loop picks the trigger up as
soon as it returns to its select, and concurrent triggers coalesce.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:48:42 +02:00
Stefan Haller a61727cd5e
Add a hint about how to use diff --color-words or --word-diff in lazygit (#5795)
Some checks are pending
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
Since this frequently comes up as a feature request (but there are
reasons why we don't want to add it), explain how to do this in lazygit
today.

See
https://github.com/jesseduffield/lazygit/pull/5784#issuecomment-4925324150.
2026-07-10 15:47:05 +02:00
Stefan Haller c81c08071f Add a hint about how to use diff --color-words or --word-diff in lazygit
Since this frequently comes up as a feature request (but there are
reasons why we don't want to add it), explain how to do this in lazygit
today.
2026-07-10 15:43:28 +02:00
Stefan Haller e59c1d1cb7
Make model<->view index conversions independent of rendering (#5785)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
The model<->view index conversions were derived from arrays that only
renderLines populated. That made them depend on the list having been
rendered (so a conversion before the first render ignored the non-model
items), and it made them go stale whenever the model changed after a
render: converting an index then returned a wrong result, and once the
model had grown past the last rendered length the conversion indexed a
too-short array and panicked (seen in cherry_pick under -race).

The conversion is a pure function of the current list length and the
current non-model items, and needs none of the rendered display strings.
Compute it directly and drop the cached arrays, so the result is always
consistent with the current model and no longer depends on rendering.
2026-07-09 15:10:31 +02:00
Stefan Haller 4e907c6b3e Compute list index conversions independently of rendering
The model<->view index conversions were derived from arrays that only
renderLines populated. That made them depend on the list having been
rendered (so a conversion before the first render ignored the non-model
items), and it made them go stale whenever the model changed after a
render: converting an index then returned a wrong result, and once the
model had grown past the last rendered length the conversion indexed a
too-short array and panicked (seen in cherry_pick under -race).

The conversion is a pure function of the current list length and the
current non-model items, and needs none of the rendered display strings.
Compute it directly and drop the cached arrays, so the result is always
consistent with the current model and no longer depends on rendering.

searchModelCommits converts every commit's index, and building the
non-model items can be O(len) mid-rebase, so it would now be quadratic;
snapshot the non-model items once via modelToViewIndexConverter instead
of rebuilding them per index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:54:15 +02:00
Stefan Haller d4a606c685 Demonstrate that list index conversions depend on rendering
ModelIndexToViewIndex and ViewIndexToModelIndex read conversion arrays
that only renderLines populates. So converting an index before the list
has been rendered ignores the non-model items (e.g. section headers) and
returns a wrong result; the same staleness makes a conversion after the
model has grown index a too-short array and panic (seen in cherry_pick
under -race).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:54:15 +02:00
Stefan Haller d94ca63e6d
Synchronize ViewBufferManager.Close with a starting task (#5786)
Fixes a race condition related to ViewBufferManager's stopCurrentTask
field.
2026-07-09 14:53:45 +02:00
Stefan Haller c21ce61729 Synchronize ViewBufferManager.Close with a starting task
Close read and called stopCurrentTask with no lock, while NewTask's
goroutine assigns it (and constructs the sync.Once it closes over) under
waitingMutex. On shutdown Close runs while a render task spawned by the
last layout is still starting, so the two raced on the field and the
once (three DATA RACE blocks under -race, e.g. cherry_pick).

Read stopCurrentTask once under waitingMutex and call the captured value
instead of re-reading the field, which establishes the happens-before
the once needs. This can't deadlock: no task holds waitingMutex across a
blocking UI-thread hop, so Close can always take it, and a task wedged in
such a hop is still bounded by the existing 3s timeout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:28:29 +02:00
Stefan Haller bda505148b
Make integration tests using commits more robust (#5782)
Some tests assert that a specific commit subject does or doesn't occur
in the main view; interactive_rebase/outside_rebase_range_select.go is
an example for this, it asserts `t.Views().Main().Content(
DoesNotContain("commit 06"))`. The problem with this kind of assertion
and our test commit naming scheme is that the diff view begins with a
"commit <hash>" line, and when that hash happens to start with "06" the
assertion matched it and failed spuriously. This was usually masked by
our MaxAttempts=2 that we currently use for integration tests (it's
quite unlikely that the commit gets a hash beginning with "06" twice in
a row). However, we want to get to a state where we can set MaxAttempts
to 1, so make this more robust by changing our naming scheme.
2026-07-09 12:02:25 +02:00
Stefan Haller d181615c31 Make integration tests using commits more robust
Some tests assert that a specific commit subject does or doesn't occur
in the main view; interactive_rebase/outside_rebase_range_select.go is
an example for this, it asserts `t.Views().Main().Content(
DoesNotContain("commit 06"))`. The problem with this kind of assertion
and our test commit naming scheme is that the diff view begins with a
"commit <hash>" line, and when that hash happens to start with "06" the
assertion matched it and failed spuriously. This was usually masked by
our MaxAttempts=2 that we currently use for integration tests (it's
quite unlikely that the commit gets a hash beginning with "06" twice in
a row). However, we want to get to a state where we can set MaxAttempts
to 1, so make this more robust by changing our naming scheme.
2026-07-09 11:56:27 +02:00
Stefan Haller 8d6d1f0908
Make scrolling down a very long diff with the scroll wheel much smoother (#5780)
When showing a very long diff (thousands of lines), scrolling down with
the mouse wheel was rather choppy; now it's very smooth and fast.
2026-07-09 09:51:15 +02:00
Stefan Haller 585c7f126d Cache each line's wrapping so scrolling doesn't re-wrap the whole buffer
refreshViewLinesIfNeeded re-wrapped every line of the buffer whenever
the view was tainted. That's cheap for short content, but scrolling a
long diff calls it constantly: adjustDownwardScrollAmount queries
ViewLinesHeight on every scroll event, and each newly-read line taints
the view, so every notch re-wrapped the entire buffer. Wrapping measures
each cell's width (uniseg) and allocates per line, so once you'd scrolled
far enough down the diff, scrolling turned sluggish - the cost grew with
how much had been read. (A CPU profile of scrolling deep in a long diff
put 77% of the time in lineWrap, reached almost entirely via
ViewLinesHeight rather than draw.)

Cache each line's wrapped result on the lineType, keyed by the width it
was wrapped at, and only re-wrap lines that have actually changed since
the last refresh. A firstDirtyLine index, updated in the same three
places that set `tainted` (write, clearViewLines' callers, SetHighlight),
marks the lowest line that might have changed; lines below it with a
matching cached width reuse their cached wrapping. The cache lives on the
line, so it's freed with the line when the view's content is replaced
(e.g. selecting a different commit) - it doesn't accumulate across a
session.

The wrapping cost per scroll now scales with the number of lines just
read, not with the total size of the buffer, so scrolling stays smooth
no matter how far down you are.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:32:35 +02:00
Stefan Haller 73d7b443ec Render content-only when a task reads more lines into a view
Reading more lines into a lazy-loaded view (e.g. a diff being scrolled)
never changes the window layout, and after the first screenful it
doesn't even change the visible content - the new lines land below the
viewport, so the only thing that changes on screen is the scrollbar
thumb. Yet each read triggered a full render: a layout pass plus a
redraw of every view. On a slow terminal that full-screen repaint on
every read is a big part of why scrolling through a not-yet-fully-read
diff stutters.

Route the task's refresh through a content-only render instead. It
skips the layout pass and only redraws the views whose content changed,
leaving tcell's cell-level dirty tracking to emit just the cells that
actually differ (in the steady state, the scrollbar column).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:32:35 +02:00
Stefan Haller cbf220c497 Read lines based on scroll position instead of a fixed per-notch delta
When scrolling a lazy-loaded view (a diff in the main view, the command
log, etc.), we top up the view's line buffer by reading more lines from
the still-running task. This was driven by asking the task to read a
fixed number of *additional* lines on every scroll event, which had two
problems:

- It was decoupled from the scroll position. Scrolling down, back up,
  and down again re-read lines that had already been read, so the buffer
  crept towards the end of the input regardless of where the user
  actually scrolled.

- A single wheel notch only bought a single notch worth of runway, so
  fast scrolling constantly outran the reader and had to wait for the
  next read (and re-render) on every notch.

Make ReadLines take an absolute target total instead of a delta: the
task tracks how many lines it has read and only reads the shortfall, so
requests are idempotent. Callers now ask to fill the viewport at the
current scroll position plus a few screenfuls of read-ahead, which gives
scrolling enough runway to stay smooth.

The four call sites all wanted the same "fill this view" computation, so
consolidate them into a single ReadLinesToFillView helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:32:35 +02:00
Stefan Haller 3491a15f6e
Fix data race with command log (#5779)
LogAction and LogCommand are called from git worker goroutines (every
command a worker runs logs itself, and controllers log an action before
kicking off their worker), where they set the Extras view's Autoscroll
flag and append to GuiLog while the UI thread reads both when it lays
out and draws the view. Bounce the writes onto the UI thread instead.

This doesn't fix any user-visible issue that I know of; labelling it as
"maintenance" rather than "bug" for that reason. It is one of many steps
that gets us closer to running our test suite with `-race`.
2026-07-09 08:45:59 +02:00
Stefan Haller 1268a589d6 Write the command log on the UI thread
LogAction and LogCommand are called from git worker goroutines (every
command a worker runs logs itself, and controllers log an action before
kicking off their worker), where they set the Extras view's Autoscroll
flag and append to GuiLog while the UI thread reads both when it lays out
and draws the view. Bounce the writes onto the UI thread instead.

Use the background variant so the bounce doesn't count towards lazygit
being busy: writing the command log is incidental display work, and a
foreground task would let an in-flight log write refuse a concurrent repo
switch (the same reason view-buffer renders and toasts are backgrounded).
Ordering between successive log calls is preserved by the bounce FIFO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 08:41:10 +02:00
Stefan Haller 73714a3b38
Fix data race with status string (#5777)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
GetStatusString and HasStatus read the statuses slice without holding
the mutex that addStatus and removeStatus take when they mutate it. The
readers run on the spinner-render worker (which polls GetStatusString
every frame) while removeStatus fires from the waiting-status and
toast-expiry goroutines, so the unguarded reads race the concurrent
writes. Take the mutex in the readers too.

This doesn't fix any user-visible issue that I know of; labelling it as
"maintenance" rather than "bug" for that reason. It is one of many steps
that gets us closer to running our test suite with `-race`.
2026-07-09 07:06:46 +02:00
Stefan Haller 2420fc7b76 Lock the status list when reading it
GetStatusString and HasStatus read the statuses slice without holding
the mutex that addStatus and removeStatus take when they mutate it. The
readers run on the spinner-render worker (which polls GetStatusString
every frame) while removeStatus fires from the waiting-status and
toast-expiry goroutines, so the unguarded reads race the concurrent
writes. Take the mutex in the readers too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 18:24:18 +02:00
Stefan Haller fe4c195370
Perform refresh model and view updates on the UI thread instead of using mutexes (#5767)
Some checks failed
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
Refresh workers do their git work on background goroutines and then
mutate the model (`Model().Commits`, `.Branches`, …) and re-render views
directly from those goroutines, racing the UI thread's own cursor and
render code. This has been a long-standing source of flaky integration
tests, and it's what prevents us from running the e2e suite under the
race detector.

This PR removes that class of races by updating refresh state only on
the UI thread, and drops the mutexes that were standing in for that
discipline. It's an internal concurrency change with no intended
difference in normal use (the one small user-facing addition is noted at
the end).

- Each refresh scope does its git work on a worker, then enqueues its
model write onto the UI thread ("bouncing") through a single primitive,
so all model mutations are serialized on the one UI goroutine alongside
the cursor/render code they used to race.
- That primitive is generation-guarded: if you switch repos while a
refresh is in flight, the queued write is dropped instead of being
applied to the new repo.
- The inputs a refresh worker reads (model fields, selection, modes) are
now captured on the UI thread up front, so the worker computes from an
immutable snapshot. Worker-issued refreshes use a dedicated entry point,
and a debug-only assertion checks that the entry point matches the
calling goroutine.
- A few flags written from workers are made atomic rather than bounced.
- All six refresh mutexes are removed as redundant; the branches mutex
is replaced by a small branch-load sequence guard so the recency-sorted
result still wins at startup.
- Repo switching now runs on the UI thread rather than a worker,
removing a race on the shared gui state.

This is one step toward being able to run the test suite under `-race`
in CI — the remaining view-buffer rendering races are left for a
follow-up.

The one user-facing addition: switching repositories while a foreground
git operation is still running is now refused with a toast, instead of
running the operation's remaining commands against the newly-switched
repo.
2026-07-07 18:14:58 +02:00
Stefan Haller 4d33d9df8b Mention the Then rule in AGENTS.md 2026-07-07 18:10:45 +02:00
Stefan Haller 19b34851ff Guard the view-render and prompt-dismiss bounces on the generation
The model-update bounces already drop themselves when the repo is
switched mid-refresh (onUIThreadUnlessRepoChanged), but three bounces
that touch the UI without writing the model did not: refreshView's
render, the staging-panel refresh, and the stale continue-rebase prompt
dismissal. All three ran unconditionally on the UI thread, so a
background refresh in flight across a repo switch could render the old
repo's data (through a context object belonging to the now-replaced
context tree), or pop the new repo's popup based on the old repo's
prompt state.

Route them through onUIThreadUnlessRepoChanged too, so they're dropped
alongside the model writes they accompany. This also fixes the dismiss
bounce using the raw foreground OnUIThread, which ignored the background
flag every other bounce in a background refresh respects.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 2c3a6acafa Thread a refreshEnv through the refresh scopes
Every refresh scope needs two ambient values to bounce its model and
view updates back to the UI thread safely: the background flag (which
picks the dispatch variant that doesn't count towards lazygit being
busy) and the repo generation that guards the bounce against a repo
switch. These were threaded separately — background as a parameter on
every refreshXxx function, generation re-read from the model inside each
one. Bundle them into a single refreshEnv passed through instead, so the
guard has a home to grow into (the next commit needs the generation in
refreshView, which currently has no access to it).

Capturing the generation once, at the start of the refresh, is also more
correct than the previous per-function re-read. The baseline should
reflect the repo whose inputs the refresh snapshotted (all captured up
front on the UI thread), not whenever each scope's worker happens to
wake. With the per-function read, a background refresh whose worker woke
after a repo switch would read the new generation and let its bounce
through, writing data computed from the old repo's inputs into the new
repo; capturing up front makes that bounce drop instead.

No behavior change for foreground refreshes, where the UI thread is held
for the whole refresh and the generation can't move under it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller fefb3b632e Clear the preserved commit message on the UI thread
The commit's gpg onSuccess runs on a worker when the command output is
streamed, so its ClearPreservedCommitMessage wrote commit-message
context state off the UI thread. Bounce that write through OnUIThread.
2026-07-07 18:10:45 +02:00
Stefan Haller 12757e2723 Swap the file-path suggestions trie on the UI thread
GetFilePathSuggestionsFunc builds the trie on a worker (the slow
AllRepoFiles walk) and then assigned Model().FilesTrie and refreshed the
suggestions panel from there, racing the UI thread that reads the trie.
Keep the build on the worker but bounce just the model assignment and
the refresh through OnUIThread.
2026-07-07 18:10:45 +02:00
Stefan Haller 6cd93de5b9 Cancel the commit-file range selection on the UI thread after discard
The discard handler cancelled the commit-files range selection from its
WithWaitingStatus worker. Bounce it through OnUIThread, keeping it after
the successful CheckMergeOrRebase as before.
2026-07-07 18:10:45 +02:00
Stefan Haller e7105a3138 Collapse the branch range selection on the UI thread after a delete
The three branch-delete handlers and the two worktree-removal
continuations collapsed the Branches/RemoteBranches range selection from
their worker goroutine, racing the UI thread. Wrap each collapse in
OnUIThread, keeping it in the same spot relative to the refresh (FIFO
preserves the collapse-then-refresh order the name-restore depends on).
2026-07-07 18:10:45 +02:00
Stefan Haller 67b0a6b1a4 Move the pull-patch panel close and focus off the worker
The pull-patch-into-new-commit handlers closed the commit-message panel
and, on success, pushed the local-commits context from inside the
WithWaitingStatus worker. Close the panel in OnConfirm before
dispatching (UI thread), and bounce the post-rebase context push through
OnUIThread, keeping it on the success path.
2026-07-07 18:10:45 +02:00
Stefan Haller f07e94afe0 Keep RebaseOntoRef's marked-base access on the UI thread
The three rebase-onto menu items read Modes().MarkedBaseCommit.GetHash()
(a bare string field) and, on success, cleared it via
ResetMarkedBaseCommit and pushed the commits context — all from the
WithWaitingStatus worker, racing the UI thread. Read the marked base
hash before dispatching, and bounce the post-rebase reset and context
push through OnUIThread, still guarded by the success check so they
don't run on the conflict path.
2026-07-07 18:10:45 +02:00
Stefan Haller 5d8c893497 Capture commits and set selection on the UI thread for squash/fixup/drop
interactiveRebaseWithFlag and dropMergeCommit ran inside the
WithWaitingStatus worker but read Model().Commits and wrote the
selection (SetSelection(startIdx)) there, racing the UI thread. Thread
the commits slice in from each caller, and hoist the pre-rebase
selection into a UI-thread helper (selectRebaseResultCommit) called
before dispatching — squash/fixup unconditionally, drop only on the
non-merge path, matching the previous action guard.
2026-07-07 18:10:45 +02:00
Stefan Haller 6c38ddc9a7 Set ResetToRef's post-reset selection via refresh intents
ResetToRef ran on a worker and wrote the local-commits and reflog
selection directly (SetSelection(0) on both) before its refresh, racing
the UI thread. Fold those into the refresh's selection intents:
SelectHeadCommit for the commits (after a reset HEAD is the top commit,
and mid-interactive-rebase it correctly picks the real head over the
first todo entry) and SelectTopReflogCommit for the reflog. The
now-atomic SetLimitCommits stays where it is.
2026-07-07 18:10:45 +02:00
Stefan Haller 6d21efb515 Make the local-commits limit-commits flag atomic
CheckoutRef and ResetToRef set this flag from their worker goroutine
(to load fewer commits for speed) while the commits refresh reads it on
the UI thread in captureCommitsState to decide how many to load — a data
race. Make it an atomic.Bool so those writes are safe where they are,
rather than routing the flag through a refresh intent. Precedent:
Branch.BehindBaseBranch.
2026-07-07 18:10:45 +02:00
Stefan Haller 2edfeac538 Capture the commit-file discard and patch-toggle reads on the UI thread
discard reads Model().Commits and the selected commit index from its
WithWaitingStatus worker; read them in HandleConfirm instead.

toggleForPatch reads the commit-files ref name from the worker, and its
startPatchBuilder call reads the context's canRebase and diff range from
there too. Capture the ref name and run startPatchBuilder in
HandleConfirm before dispatching; PatchBuilder.Start only assigns
fields, so moving it off the worker changes no timing.

discard still collapses the range selection from the worker; that write
is a separate concern, left for a follow-up.
2026-07-07 18:10:45 +02:00
Stefan Haller 462d75232b Look up the submodule file and branch worktree on the UI thread
ResetSubmodule and fastForward each call a helper that reads the model
from inside their worker: FileForSubmodule reads Model().Files and
worktreeForBranch reads Model().Worktrees, racing the UI thread's model
writes. Hoist both lookups above the worker dispatch.
2026-07-07 18:10:45 +02:00
Stefan Haller fceba31212 Capture moveCommitsToNewBranch's model reads on the UI thread
The two move helpers run inside the WithWaitingStatus worker that
withNewBranchNamePrompt dispatches to, but read Model().Files/Submodules
(to decide whether to auto-stash) and Model().Commits (the unpushed
commits to cherry-pick off the base branch) from there, racing the UI
thread's model writes. Compute mustStash — needed by both paths — at the
top, and the unpushed commits in the off-of-main menu item, on the UI
thread, and pass them into the helpers.
2026-07-07 18:10:45 +02:00
Stefan Haller b4a976834f Capture reword/amend/author commit reads on the UI thread
handleReword, amendTo, and the reset/set/add-co-author handlers pass
Model().Commits (and the selected line index) to a git rebase from
inside the WithWaitingStatus worker, racing the UI thread's model
writes. Read them on the UI thread before dispatching.

The author handlers index the full commit list by absolute start/end, so
the range sub-slice withItemsRange hands amendAttribute is not what they
need; capture the full Model().Commits there and thread it through.
2026-07-07 18:10:45 +02:00
Stefan Haller eb95ae15f3 Capture the custom-patch handlers' commit reads on the UI thread
These handlers dispatch their rebase to a worker via WithWaitingStatus
but read Model().Commits (and, for move-to-selected-commit, the selected
line index) from inside that worker, racing the UI thread's model
writes. Read them on the UI thread before dispatching and close over the
results.

getPatchCommitIndex stays as-is: moving its call out of the worker makes
its own Model().Commits read UI-thread-bound too, so the identical copy
in patch_building_controller.go needs no matching signature change.

The two pull-patch-into-new-commit handlers still push a context and
close the commit-message panel from the worker; those writes are a
separate concern, left for a follow-up.
2026-07-07 18:10:45 +02:00
Stefan Haller 5162a768eb Guard every refresh's entry point, not just the commits scope
With every scope's worker reads now captured on the UI thread and every
worker caller on RefreshFromWorker, the debug entry-point assertion no longer
needs to be scoped to the commits refresh. Move it to the top of
performRefresh so it guards every refresh regardless of which scopes it
touches, and drop the per-scope gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller fd6b20847a Capture the files, reflog, branches and stash refresh inputs
The remaining refresh scopes each still read model, context, and mode state
directly on their worker, racing the UI thread — the same class of race the
commits refresh had:

- files reads Model.Files (to detect resolved conflicts and drive the
  auto-stage) and the Files context's ForceShowUntracked;
- reflog reads the existing reflog slices (for the incremental fetch),
  Model.HashPool and the filtering path/author;
- branches reads Model.MainBranches and the previous branches (for the
  BehindBaseBranch carry-over);
- stash reads the filtering path.

Gather each scope's inputs into an immutable snapshot on the UI thread (via
captureOnUIThread) before dispatching the git work, and have the refresh
compute from the snapshot — for branches, threaded through both the immediate
and the recency-sorted startup loads, which share one snapshot (the
BehindBaseBranch carry-over is identical either way). Status, tags and
worktrees read nothing UI-owned, so they're left alone.

For the snapshots to actually run on the UI thread, the worker callers that
reach these scopes must announce themselves: convert the submodule
operations, the submodule stash-and-reset, and the background files poller
to RefreshFromWorker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 3e5c99e1e4 Make the started-in-lazygit and startup-stage flags atomic
GuiRepoState.mergeOrRebaseStartedInLazygit and StartupStage are plain
fields, but they're written and read from worker goroutines: the former
from both the files refresh and the merge/rebase result path (which runs on
a worker for the async callers), the latter from the reflog/branches load as
it transitions the startup stage. Those are data races.

Make both atomic, like Branch.BehindBaseBranch. They're leaf flags, not
mutexes guarding model or view state, so an atomic is the natural fit and
keeps the merge/rebase result path out of this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 56989922e4 Capture the remotes/sub-commits/commit-files/rebase-commits inputs
These four refreshes each read model, context, and mode state directly on
their worker — the same class of race the commits refresh had:

- remotes reads the selected remote (Contexts().Remotes.GetSelected), needed
  to keep the remote-branches selection valid;
- sub-commits reads the SubCommits ref/limit/divergence, the filtering
  path/author, and Model.MainBranches/HashPool;
- commit-files reads the diff endpoints (CommitFiles from/to and the diffing
  args);
- rebase-commits reads Model.HashPool/Commits.

Give each the same treatment as commits: gather its inputs into an
immutable snapshot on the UI thread (via captureOnUIThread, inline for a
UI-thread refresh, hopped for a worker one) before dispatching the git work,
and have the refresh compute from the snapshot. The commit-files re-init
inside the commits refresh captures its endpoints in the bounce, right after
ReInit sets them, before dispatching to the worker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 988d04bda9 Assert a refresh uses the entry point matching its goroutine
Now that every commits-reaching refresh issued from a worker goes through
RefreshFromWorker, guard the choice: in debug builds, panic if a refresh was
issued from the UI thread as RefreshFromWorker or from a worker as Refresh.

The caller's own goroutine is recorded at the top of performRefresh, before
a BLOCK_UI refresh dispatches onto the UI thread, so the check holds for
every mode rather than being fooled by BLOCK_UI. It's scoped to the commits
refresh for now, the only converted scope; once the rest are converted the
guard can move up to cover every refresh unconditionally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 558fd2c9d3 Route merge/rebase result handling to the right refresh entry point
CheckMergeOrRebaseWithRefreshOptions refreshes after a merge/rebase step,
and until now always via the UI-thread Refresh. Most of its callers are on a
worker (the WithWaitingStatus/WithInlineStatus merge, squash-merge, rebase,
pull, amend, drop, and patch-move handlers), so that refresh reads the
commits scope off the UI thread — the race the previous commit addresses for
everything else.

Split it: the default is for worker callers and refreshes via
RefreshFromWorker; a new CheckMergeOrRebaseWithRefreshOptionsFromUIThread is
for the handlers that run the step synchronously on the UI thread
(WithWaitingStatusSync, kept sync so rapid key presses batch): move up/down,
revert, squash-fixups, cherry-pick paste, and patch-discard.

The two share a private impl carrying which thread the caller is on, and the
auto-skip recursion (genericMergeCommandImpl for an empty commit) threads it
through so the follow-up step refreshes on the same thread. The
merge-and-commit refresh in SquashMergeCommitted, also on a worker, moves to
RefreshFromWorker to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 080542c9fb Capture the commits refresh's inputs on the UI thread
A commits refresh does its git work on a worker and then reads the model,
the contexts, and the modes for that work directly from there:
LocalCommits.GetSelectionRangeAndMode/GetLimitCommits/GetShowWholeGitGraph,
Model.Commits/MainBranches/HashPool, the filtering path/author. Those are
owned by the UI thread, which is concurrently running the cursor and render
code, so the reads race it — the dominant, confirmed source of the
commits-scope flakes (the startup ClampSelection vs GetSelectionRangeAndMode
race, for one).

Gather them into an immutable capturedCommitState on the UI thread, before
the git work is dispatched, and have refreshCommitsWithLimit compute from
that snapshot. UI-thread callers capture inline; worker callers can't (a
SYNC/BLOCK_UI refresh parks the UI thread at wg.Wait, so hopping from a
scope sub-worker would deadlock), so the capture is lifted out of the scope
worker into the refresh orchestration, and worker callers announce
themselves with a new RefreshFromWorker entry point that hops the capture to
the UI thread and blocks for it (OnUIThreadAndWait). BLOCK_UI runs the whole
refresh on the UI thread regardless of the caller, so it captures inline
too.

Every refresh issued from a worker that reaches the commits (or branches,
which pulls in commits) scope is converted: the fast-forward, branch/tag
delete, worktree remove/detach, push, reword-via-rebase, author edits,
custom-command, hard-reset-with-autostash, reset-to-ref, fetch-and-checkout,
gpg-stream, post-fetch, and external-change-poller refreshes, plus the
branch checkout and move-commits-to-new-branch refreshes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +02:00
Stefan Haller 1def541acb Add IsUIThread and OnUIThreadAndWait to gocui
The next commits move refresh workers to read UI-thread-owned state (the
model, contexts, selection) on the UI thread rather than off it. Two
primitives support that:

- OnUIThreadAndWait runs a function on the main event loop and blocks the
  caller until it has run, so a worker can read that state without racing.
  OnUIThreadAndWaitBackground is the same for background routines, whose
  work must not count towards the program being busy.
- IsUIThread reports whether the caller is on the main event loop, for a
  debug-only assertion that a refresh was issued from the thread it claims.
  It records the main loop's goroutine id in MainLoop and compares via
  goid, so it's promoted from an indirect to a direct dependency.

goid is used only by that debug assertion, never to drive production
control flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller f0ea537956 Fetch pull requests using the freshly-loaded branches and remotes
The PR fetch needs the current branches (for their upstreams) and
remotes to know what to query. It read them from Model().Branches /
Model().Remotes on its own worker, after waiting on branchesAndRemotesWg
for the branches and remotes refreshes to finish.

That wait no longer guarantees fresh data: those refreshes now write the
model in a bounce onto the UI thread, and Done() fires before the bounce
has been processed. So the fetch read the pre-refresh lists — most
visibly, checking out a branch that has a PR wouldn't show that PR until
the next refresh, because the fetch queried the old branch set.

Have refreshBranches / refreshReflogAndBranches / refreshRemotes return
what they loaded, stash it in locals in Refresh, and hand it to the
fetch. The wait on branchesAndRemotesWg orders the fetch after both
loads have stored their slices, so it fetches against exactly the
branches and remotes that were just loaded, with no model read on the
worker. The previous commit guarantees both are always in scope when
pull requests are, so no fallback is needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 23cfa9b070 Also refresh branches and remotes when refreshing pull requests
The pull-request fetch queries GitHub for the tracking branches'
upstreams against the configured remotes. It therefore depends on the
branches and remotes being up to date; a refresh that asks for pull
requests but not for those (e.g. checking out a branch) would fetch
against a stale branch/remote list — for instance missing the PR of the
branch just checked out.

Expand the scope so pull requests always co-refresh branches and
remotes. This also sets up the next commit to hand the freshly-loaded
branches and remotes straight to the fetch, instead of reading them
back from the model (which, now that those writes are bounced onto the
UI thread, would be stale on the worker).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 7c4d8045f9 Clamp the commit-file tree selection when the tree is rebuilt
CommitFileTreeViewModel embedded the low-level tree's SetTree, which
rebuilds the node list without touching the cursor. So after a shrinking
rebuild (e.g. moving a patch out into the index removes a file), the
selection index could be left past the end of the tree. GetSelectedItems
then indexes out of range and returns a nil node, which segfaults callers
such as canEditFiles when the options map is rendered during layout.

Override SetTree to ClampSelection after the rebuild. Unlike
FileTreeViewModel we deliberately don't also re-find the selected node by
path: that walk lands on the containing directory when a file is removed
from a dir that then collapses, whereas keeping the clamped index lands
on the sibling file (see discard_old_file_changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller bd6081d601 Select the checked-out branch via a refresh intent, not off-thread
Operations that check something out (checkout, create branch, move
commits to a new branch, fetch-and-checkout) selected the newly
checked-out branch by calling SelectFirstBranchAndFirstCommit() before
the refresh and passing KeepBranchSelectionIndex so the refresh wouldn't
override it. That set the selection directly, usually from a worker
goroutine (WithWaitingStatus/WithInlineStatus). Now that the refresh's
own selection write is bounced onto the UI thread, the two writes could
land in either order, and under load the refresh's "restore the
previously-selected branch" write would win — leaving the old branch
selected instead of the new one (flaky
move_commits_to_new_branch_from_base_branch).

Replace it with declarative selection intents applied inside the
refresh's own bounce, so the selection is set on the UI thread and
atomically with the list write (no off-thread write, and no BLOCK_UI
needed to avoid a flicker):

- BranchSelection: SelectCheckedOutBranch selects the checked-out branch
  (top of the list). The default, KeepBranchSelectionByName, restores
  the previously-selected branch by name as before. This replaces the
  KeepBranchSelectionIndex bool.
- CommitSelection: SelectHeadCommit (already existed) for the commit.
- SelectTopReflogCommit selects the top reflog entry, since a checkout
  adds a new entry there (reflog/checkout relies on this).

SelectFirstBranchAndFirstCommit is gone. The previously-selected branch
is now read at the top of the branches bounce, before the list is
overwritten, so that read moves onto the UI thread too.

fetchAndCheckout's refresh changes from ASYNC to SYNC so its
post-refresh focus switch can run in Then on the UI thread; SYNC keeps
the inline fetch spinner spinning (only BLOCK_UI would freeze it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 5414daf492 Exclude toast rendering from the busy query
A toast keeps a foreground spinner task alive for its whole lifetime
(~2-4s): showing one calls renderAppStatus, whose OnWorker loop runs
until the status string clears. With the repo-switch guard in place that
made the guard's own "can't switch, operation in progress" toast keep
Busy() true, so the next escape/switch was refused until the toast
faded — you had to wait it out.

Render toasts in the background, like view-buffer content: a toast is a
transient notification, not lazygit driving an operation, so a switch
during one is fine. A real operation that shows a toast still keeps its
own foreground task busy independently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 56932abe06 Refuse a repo switch while a foreground operation is in flight
Switching repos reassigns gui.git and the process cwd; doing it while a
foreground git operation (rebase/commit/push/…) is mid-flight would run
that operation's remaining commands against the wrong repo. The same
applies while the refresh an operation triggers is still settling: its
model writes are generation-guarded, but the client-side Then/OnUIThread
callbacks that run after it aren't, and shouldn't run against a repo that
changed underneath them.

Refuse the switch (with a toast) whenever gocui reports a busy foreground
task. DispatchSwitchTo carries the guard for the simple callers. The
callers that do work before the switch check up front instead, so a
refused switch doesn't leave that work half-done: worktree creation
checks before creating (its own waiting-status spinner would otherwise
make the query busy and refuse its own switch); submodule-enter and the
recent-repos menu check before mutating the repo-path stack (pushing /
clearing it); and escape-to-parent (SwitchToParentRepo) checks before
popping it, so a refusal doesn't consume the entry and strand the user
with nowhere to escape back to. All then call the unguarded switchTo,
which is safe because their own operation is complete by then.
2026-07-07 18:09:33 +02:00
Stefan Haller 8655d3f5a5 Exclude view-buffer render tasks from the busy query
The repo-switch busy query must not count view-buffer content rendering:
those tasks paint a view rather than drive a git operation, so leaving
one running across a switch is harmless (the switch's own refresh
re-renders). More importantly, they fire on nearly every focus/selection
change — including the context activation that runs right before a
menu/prompt confirmation handler (e.g. confirming worktree creation).
A synchronous busy check in such a handler would otherwise see that
render and make the very switch the handler is about to request refuse
itself.

Route ViewBufferManager's tasks through a new gocui NewBackgroundTask so
they're tracked for idle detection but excluded from the busy query. The
task "background" flag now covers two kinds of non-blocking work: the
background routines (and their refreshes) tagged earlier, and view
rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller d95900ccd0 Tag background routines and their refreshes as background tasks
For the busy query to be usable as a repo-switch guard it has to be
false while the ongoing background routines run, or a switch would be
refused every time a background fetch or files refresh happened to be in
flight. Mark that work as background so it's excluded from the query.

The background routine dispatch in goEvery becomes OnWorkerBackground,
and the auto-fetch waiting status renders its spinner through the
background variants. Within a refresh, the background flag (which
Refresh already carries as options.Background, and which the files path
already threaded) is now threaded through every place that enqueues a
task: the async scope workers, the model-write bounces
(onUIThreadUnlessRepoChanged), refreshView, the staging bounce, the
Then dispatch, and the branch-loader's behind-count worker. Two
single-caller chains reached by a background files refresh get the flag
too: MergeConflictsHelper.EscapeMerge and BranchesHelper.
AutoForwardBranches (whose follow-up refresh must stay background when
triggered by the background fetch).

Nothing gates on the busy query yet, so this is behavior-preserving;
background tasks still count as busy for the test idle-listener, which
looks at every task regardless of the background flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller e352cafd43 Add background tasks and a synchronous busy query to gocui
Repo-switch safety needs to answer, synchronously on the UI thread,
"is any foreground work in flight right now?" so it can refuse a switch
that would run against a repo about to be swapped out. gocui already
tracks a task per OnWorker/Update for the test idle-listener; extend
that.

Tasks gain a background flag: background tasks (the ongoing routines
like auto-fetch, and the refreshes they trigger) don't count towards
busy, because their model writes are already guarded against a
concurrent switch by the repo generation. Add OnWorkerBackground,
UpdateBackground and UpdateContentOnlyBackground (plus the gui-layer
OnUIThreadBackground / OnUIThreadContentOnlyBackground / OnWorkerBackground
on IGuiCommon) so the few background call sites can opt in without
touching the hundreds of foreground callers.

TaskManager.hasBusyForegroundTaskExcept answers the query; Gui.Busy()
wraps it, excluding the event currently being processed (recorded as
currentTask) so a handler asking the question doesn't count itself.

Nothing gates on Busy() yet; this is the mechanism only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller cf7c3d82e6 Run the repo switch on the UI thread
DispatchSwitchTo wrapped its whole body in WithWaitingStatus, so the
switch ran on a worker: it chdirs, reassigns gui.git, and swaps gui.State
(in resetState), all of which the UI thread also reads. The generation
guard prevents the refresh-in-flight logical corruption but not this
pointer data race on gui.State.

Run the switch synchronously on the UI thread instead. Every caller is
already a UI-thread handler except NewWorktreeCheckout, which must create
the worktree (git work) on a worker first; it now dispatches only the
switch via OnUIThread. The heavy data loading still happens
asynchronously via the refresh that onNewRepo triggers, so the
synchronous part is small (a couple of git rev-parse plus direnv).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 3103fe97ea Replace RefreshingBranchesMutex with a branch-load sequence guard
This removes the last refresh mutex. RefreshingBranchesMutex wasn't
guarding a data race (Branch.BehindBaseBranch is atomic, and every model
write is now bounced onto the UI thread); it was serializing the two
branch loads that race at the INITIAL startup stage — an immediate one
sorted without the reflog, and an async one that loads the reflog and
sorts by recency — so that the recency-sorted write landed last and won.
That serialization was never a real guarantee, only "very likely": it
relied on the immediate load acquiring the lock before the async load,
which had to load the reflog first.

Instead, each branch load takes a monotonically increasing sequence
number, and its bounce drops the write if a later-started load has
already applied. Combined with the preceding commit (immediate load runs
before the async one is spawned), this is an actual guarantee: the
immediate non-recency load always has a lower sequence than its recency
async partner, so the highest sequence number is always held by a
recency-sorted load, and highest-wins converges on recency ordering —
even if more refreshes fire during the INITIAL window, since each
refresh's async out-sequences its own immediate.

The guard also subsumes what the mutex gave post-startup: a slow, stale
refresh's bounce can no longer clobber a newer refresh's branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 6d8ab1d063 Run the immediate startup branch refresh before spawning the async one
At the INITIAL startup stage two branch refreshes happen: an immediate
one sorted by whatever reflog we have (empty, so not by recency), and an
async one that loads the reflog first and re-sorts by recency. Until now
the async one was spawned first and the immediate one ran afterwards;
this inverts that so the immediate refresh runs before the async one is
spawned.

With RefreshingBranchesMutex still in place this is behavior-preserving
(the mutex serializes the two either way). It's a preparatory step for
replacing that mutex with a branch-load sequence guard: running the
immediate refresh first establishes a happens-before relation between
the two loads' sequence numbers, so the recency-sorted one is guaranteed
the higher sequence.

This also lets refreshReflogCommitsConsideringStartup fold into
refreshReflogAndBranches, whose two-phase logic is now all in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 805738034f Remove refresh mutexes made redundant by bouncing
Now that every refresh scope writes its model updates on the UI thread
via onUIThreadUnlessRepoChanged, the per-scope mutexes that used to
serialize concurrent worker-goroutine access are redundant:
Model().Commits, .SubCommits, .Authors, the status view content, and
.PullRequests/.PullRequestsMap are all now written only on the UI
thread, and their readers already ran there. setSubCommits only existed
to take the lock, so it's inlined to match refreshSubCommitsWithLimit,
which writes Model().SubCommits directly.

The worker phases still *read* some of these fields (the commit
selection range, MergeRebasingCommits), but those reads race a
concurrent refresh's bounced write regardless of the mutex — the write
happens in the bounce, outside the locked region — so the mutex never
protected them. That residual read race belongs to the broader -race
effort, not to these locks.

RefreshingBranchesMutex is deliberately kept. It is load-bearing for a
reason unrelated to data races: at the INITIAL startup stage two
refreshBranches run concurrently — an immediate one with an empty
reflog (non-recency order) and an async one with the freshly-loaded
reflog (recency order). The mutex serializes them so the recency write's
bounce is enqueued last and wins. Without it the stale non-recency write
can land last, reordering the branches list (caught by the recency-sort
e2e tests: cherry_pick/*, branch/rebase_*).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller f7a61443fa Bounce BRANCHES model updates onto the UI thread
refreshBranches now loads the branches (and worktrees) on the worker and
writes Model.Branches, the pull-requests map, Model.Worktrees, and the
restored branch selection in an onUIThreadUnlessRepoChanged bounce. The
selection restore and rebuildPullRequestsMap run in the bounce so they
see the branches we just wrote; the LocalCommits re-render (for branch
head visualization) moves into the same bounce.

refreshStatus is adjusted to read the checked-out branch and the linked
worktree name inside its bounce rather than on the worker: both derive
from models (Branches, Worktrees) that are now written via bounces, so
reading them on the worker would format the status from stale values —
which showed up as the status line dropping the "(worktree)" suffix right
after entering a submodule or switching worktrees. The git work
(WorkingTreeState) stays on the worker.

Two callers that read the branches model right after a SYNC branches
refresh move their reads into Then:
- BranchesHelper.PostFetchRefresh: AutoForwardBranches reads Model.Branches,
  so it runs in Then (preserving that a fetch error is still returned to
  the caller and that background auto-forward errors aren't surfaced as a
  popup).
- BranchesController rename: the re-select-by-name loop runs in Then.

RefreshingBranchesMutex is left in place for the mutex cleanup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 549df17279 Bounce COMMITS model updates onto the UI thread
refreshCommitsWithLimit now loads the commits, working-tree state and
bisect info on the worker and writes them all — Model.Commits,
Model.BisectInfo, Model.WorkingTreeStateAtLastCommitRefresh,
Model.CheckedOutBranch, the authors, and the restored commit selection —
in a single onUIThreadUnlessRepoChanged bounce. The selection restore
(SelectHeadCommit / KeepCommitSelectionByHash) has to run in the bounce
because it reads the freshly-loaded commits; the FocusLine scroll is
enqueued from within the bounce so it still runs after refreshView's
re-render, as before.

refForLog no longer writes Model.BisectInfo as a side effect; it returns
the bisect info it read, and the bounce writes it, keeping that model
write on the UI thread. No caller reads Model.BisectInfo synchronously
after a refresh (the bisect controller reads Git().Bisect.GetInfo()
directly), so this is safe.

refreshCommitsAndCommitFiles's post-refresh re-init of the commit files
context depends on that restored selection, so it reads the selection in
a bounce and dispatches the commit-files git work back to a worker.

LocalCommitsMutex / AuthorsMutex are left in place for the mutex cleanup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 4c3f8b51ea Bounce PULL_REQUESTS model updates onto the UI thread
refreshGithubPullRequests and setGithubPullRequests now do their network
work on the worker and write Model.PullRequests / PullRequestsMap in an
onUIThreadUnlessRepoChanged bounce (the "no github remotes" and "no base
remote" early-returns clear them the same way). rebuildPullRequestsMap
moves into the bounce so the map is built from Model.Branches and
Model.Remotes as they stand on the UI thread — after those scopes'
refreshes have applied their own bounces — rather than from whatever the
worker happened to see.

The remaining worker-side reads of Model.Branches (to pick which upstream
branches to query) are the same not-yet-addressed worker-read race that
applies to the other bounced scopes.

RefreshingPullRequestsMutex is left in place for the mutex cleanup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 4c9fdc4221 Bounce STATUS view update onto the UI thread
refreshStatus computes the status line on the calling goroutine (as
before) but now writes it to the status view in an
onUIThreadUnlessRepoChanged bounce rather than calling SetViewContent
directly from the worker. RefreshingStatusMutex is left in place for now;
it only guards the compute phase between concurrent callers and comes out
in the mutex cleanup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 063bba6b45 Bounce REFLOG model updates onto the UI thread
refreshReflogCommits now does the git fetch on the worker and computes
the new ReflogCommits / FilteredReflogCommits values (still reading the
existing slices for the incremental prepend), then writes them in an
onUIThreadUnlessRepoChanged bounce. The freshly-computed reflog is still
returned for the branches load, so recency sorting is unaffected by the
write now landing on the UI thread (see the previous commit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller bd47106d03 Thread reflog commits explicitly into the branches load
BranchLoader.Load reads the reflog commits to sort branches by recency.
Today it reads them straight from Model.ReflogCommits, which works
because in the recency path the reflog refresh writes that field
synchronously just before the branches refresh reads it (same goroutine,
sequential).

An upcoming commit bounces the reflog model write onto the UI thread, at
which point Model.ReflogCommits wouldn't be updated yet when branches
runs — branches would sort by the previous refresh's reflog. To decouple
the branches load from *when* that write lands, pass the reflog commits
to refreshBranches explicitly: refreshReflogCommits now returns the
commits it loaded, and the recency path hands them straight to
refreshBranches. The non-recency path (branches and reflog run
concurrently, as before) keeps passing Model.ReflogCommits.

Pure refactor: behavior is identical, since the value passed is exactly
what Load read from the model before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller db5eb6fd39 Bounce REMOTES model updates onto the UI thread
refreshRemotes now loads the remotes on the worker and writes
Model.Remotes, rebuilds the pull-requests map, and updates the selected
remote's RemoteBranches inside an onUIThreadUnlessRepoChanged bounce.

RemotesController.addAndCheckoutRemote read Model.Remotes right after its
SYNC REMOTES refresh to select the newly-added remote; since that write
now bounces, the selection (and the follow-up fetch) move into Then so
they run against the post-refresh model rather than the stale one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 0f85c2b2b4 Bounce SUB_COMMITS model updates onto the UI thread
refreshSubCommitsWithLimit now loads the sub-commits on the worker and
writes Model.SubCommits (and folds their authors into Model.Authors via
RefreshAuthors) inside an onUIThreadUnlessRepoChanged bounce.

SubCommitsMutex and AuthorsMutex are left in place: the former is shared
with setSubCommits, the latter with the commits refresh's RefreshAuthors
call, so both come out only once those other writers are on the UI thread
too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 559b4bf298 Bounce REBASE_COMMITS model updates onto the UI thread
refreshRebaseCommits now computes the merged rebasing commits and working
tree state on the worker and writes Model.Commits /
WorkingTreeStateAtLastCommitRefresh in an onUIThreadUnlessRepoChanged
bounce. LocalCommitsMutex is left in place for now; it's shared with the
commits and branches refreshes and comes out once they're all bounced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller d6f6d0ceba Bounce WORKTREES model updates onto the UI thread
refreshWorktrees now writes Model.Worktrees in an
onUIThreadUnlessRepoChanged bounce. loadWorktrees becomes a pure loader
that returns the worktrees instead of writing them, since it's shared
with refreshBranches; refreshWorktrees bounces the result, and the
branches call site writes it directly for now (that write moves into
refreshBranches's own bounce when that scope is migrated).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller ff7ecf2d2a Bounce STASH model updates onto the UI thread
refreshStashEntries now loads the stash entries on the worker and writes
Model.StashEntries in an onUIThreadUnlessRepoChanged bounce.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 21f1dc3366 Bounce TAGS model updates onto the UI thread
refreshTags now captures the repo generation, loads the tags on the
worker, and writes Model.Tags in an onUIThreadUnlessRepoChanged bounce
rather than directly from the worker goroutine.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller b203ec57ac Bounce COMMIT_FILES model updates onto the UI thread
refreshCommitFilesContext now enqueues the Model.CommitFiles write and
CommitFileTreeViewModel.SetTree() call via OnUIThread, instead of running
them directly on the worker goroutine that drives async refreshes. This is
what makes moving SwitchToDiffFilesController's post-refresh work into Then
(previous commit) actually necessary, rather than just future-proofing.

Same repo-switch hazard as the FILES bounce, closed the same way: it
captures the repo generation before the git work and bounces through
onUIThreadUnlessRepoChanged, so the write is dropped if the user switched
repos while it was in flight.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 6203a4e411 Move post-COMMIT_FILES-refresh work into Then
SwitchToDiffFilesController.enter calls SelectPath and Context.Push
right after a (SYNC, by default) COMMIT_FILES refresh. This works today
because the model write currently happens synchronously in the worker
before Refresh's wg.Wait() returns, but an upcoming commit will bounce
that write onto the UI thread instead, at which point wg.Wait() no
longer guarantees it's been applied, and SelectPath would operate on a
stale tree.

Move both calls into Then ahead of that change, for the same reason as
the earlier FILES-scope commit: Then is already queued via OnUIThread,
so this is behavior-preserving on its own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 2c139b6ac1 Remove RefreshingFilesMutex/FileTreeViewModel.RWMutex, dead code
FileTreeViewModel.RWMutex is removed along with the
withFileTreeViewModelMutex wrapper in FilesController that RLocked it:
every writer (the bounce closure, previous commit) and every reader (key
handlers, disabled-reason callbacks) now runs on the UI thread, so the
mutex is redundant.

RefreshingFilesMutex is removed entirely, including its last use in
repos_helper's DispatchSwitchTo. That use predates the bounce and was
never about FilesController's optimistic-rendering concern; it serialized
a repo switch's onNewRepo() against an in-flight FILES refresh for the
repo being switched away from, so that a slow refresh from the old repo
couldn't write into the freshly-reset model for the new one. Bouncing the
write already broke that guarantee on its own terms — the mutex's critical
section never covered the bounced closure's actual execution, only the
(now-removed) code that enqueued it — so by this point it was only still
locked here without protecting anything real; the previous commit's
repo-generation guard is what now actually closes that race, making this
lock fully redundant rather than just relocated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller be897ce55e Bounce FILES model updates onto the UI thread
refreshStateFiles now does its git work on the worker and enqueues a
single OnUIThread closure that writes Model.Submodules, Model.Files, and
the FileTreeViewModel state together, instead of writing them directly
from the worker goroutine. refreshStateSubmoduleConfigs becomes a pure
getter (returns the configs; no model write) so the result can be
threaded into that same bounce.

The STAGING handler wraps RefreshStagingPanel in OnUIThread after
fileWg.Wait() so it sees the post-bounce file model rather than the stale
pre-refresh one — without this it would race the files bounce queued just
above it.

Bouncing the write opens a hazard the old synchronous write didn't have:
if the user switches repos while this refresh is in flight, the queued
closure would fire after resetState has replaced the model with a fresh
one for the new repo, silently overwriting it with the previous repo's
files. Guard against this with a repo generation: resetState bumps a
counter on every switch, refreshStateFiles captures it before its git
work, and onUIThreadUnlessRepoChanged drops the bounce if the generation
has moved on. This one helper is the general mechanism the remaining
scopes' bounces will use too; the same guard covers the rebase-continue
prompt, which reads Model.Files right after.

A generation counter, not a comparison of the *Model pointer: switching
away from and back to a repo reuses that repo's cached state (the same
Model pointer), which a pointer comparison would wrongly accept even
though the in-flight data is stale.

PromptToContinueRebase's Then callback (previous commit) now gets an
explanatory comment, since this is the commit that makes it necessary.

The explicit locking around these writes (RefreshingFilesMutex in
refreshFilesAndSubmodules, FileTreeViewModel.RWMutex around the write in
refreshStateFiles) is left in place for now even though it's becoming
redundant, to keep this commit focused on the bounce itself; it's removed
next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller ea83f50dc3 Move post-FILES-refresh model reads into Then
PromptToContinueRebase and WithEnsureCommittableFiles both read
Model.Files right after a SYNC FILES refresh. This works today because
the model write currently happens synchronously in the worker before
Refresh's wg.Wait() returns, but an upcoming commit will bounce that
write onto the UI thread instead, at which point wg.Wait() no longer
guarantees it's been applied.

Move both reads into Then ahead of that change. Then is already queued
via OnUIThread (previous commit), so this is a behavior-preserving
refactor on its own: the model is fully written by the time Then runs
either way, whether that write is still synchronous or gets bounced
later.

As part of restructuring WithEnsureCommittableFiles, prepareFilesForCommit
and syncRefresh are inlined into their single call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 717448f105 Make RefreshOptions.Then a func() error, queue it via OnUIThread
This is preparation for upcoming commits that will bounce refresh-scope
model updates (e.g. Model.Files) onto the UI thread by enqueuing the
write via OnUIThread instead of applying it directly on the worker
goroutine. Once that lands, a Then callback that reads the model must
run after that queued write has been processed, not synchronously at
wg.Wait() time — at that point the workers have returned, but a bounce
they queued may not have been processed yet.

Queuing Then via OnUIThread here, ahead of that change, guarantees the
right ordering once it lands: a bounce queued earlier in the same
refresh is already sitting in the channel by the time wg.Wait()
returns, so Then enqueued after it will always be processed after, and
see the post-refresh model.

The signature change to func() error lets Then propagate errors
through gocui's normal error handler (the same path key-handler errors
take).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller b54d4c369b Remove unused IsRefreshingFiles state
GetIsRefreshingFiles() is never called anywhere in the codebase, so the
flag serves no purpose. Remove it from Gui, StateAccessor, and
IStateAccessor, and drop the two SetIsRefreshingFiles calls in
refreshFilesAndSubmodules that maintained it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:09:33 +02:00
Stefan Haller 5350b6c37b Remove unused CommitFileTreeViewModel.RWMutex 2026-07-07 18:09:33 +02:00
Stefan Haller dd1576138a AGENTS.md additions 2026-07-07 18:09:33 +02:00
Stefan Haller fa9429f8c7
Fix stuck inline status when pushing/fetching (#5768)
Operations that show an inline status next to the item they operate on
("Pushing", "Fast-forwarding", "Fetching", …) would sometimes fail to
remove this inline status when done.

Fixes #5534.
2026-07-07 18:08:19 +02:00
Stefan Haller 78dd678ce5 Drop the now-redundant manual re-render in tag push
Pushing a tag triggers no refresh, so it used to redraw the tags view
by hand to remove the "Pushing" inline status. WithInlineStatus now
always re-renders after clearing the operation, so this is redundant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 17:46:16 +02:00
Stefan Haller 775ebc9e44 Re-render to clear an inline status when its operation finishes
Operations that show an inline status ("Pushing", "Fast-forwarding",
"Fetching", …) removed it by relying on the async refresh they trigger
to redraw the view after the item operation had been cleared. That
ordering was never guaranteed: the item operation is cleared on the
worker once the operation's function returns, while the refresh redraws
the item from the UI thread whenever its (asynchronous) git work
happens to finish. If the refresh redrew before the clear, the status
was left on screen with no later redraw to remove it, so the branch (or
tag/remote) stayed stuck showing e.g. "Pushing" indefinitely even though
the operation had completed. This is timing-dependent, which is why it
surfaced as rare, hard-to-reproduce reports and as flaky CI failures.

Fix it by re-rendering in stop() right after clearing the operation,
and by making these refreshes synchronous rather than async. Because a
synchronous refresh has already updated the model and queued its own
redraw by the time stop() runs, and UI-thread callbacks run in order,
the redraw we queue here runs last and draws the up-to-date model with
the status removed. An async refresh couldn't give that guarantee: its
model update might not have landed yet, so the redraw could briefly
flash the pre-operation status.

Pull refreshes through the shared CheckMergeOrRebaseAndSelectHeadCommit,
so that helper becomes synchronous too; its only other caller,
RegularMerge, thereby also refreshes synchronously, which is fine: a
synchronous on-worker refresh is what we want anyway.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 17:46:16 +02:00
Stefan Haller 3537f855b0
Show a spinner for more long-running operations (#5765)
Some checks are pending
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
Some operations used to freeze lazygit while they ran, with no sign that
anything was happening — the UI would just lock up until they finished.

This affected:

- Merging a branch (including squash merges)
- Rebasing a branch interactively onto another ref
- Setting a commit to "edit"
- Resetting to a commit, branch, or tag
- Continuing, skipping, or aborting a merge or rebase

Many of these were usually fast under normal conditions (e.g. a hard
reset when the head doesn't change, or a "rebase --continue" when
there's only a handful of commits left), but in some cases they could
take long (e.g. a hard reset to some distant commit where lots of files
changed). Now each of these shows a spinner while it works and keeps the
UI responsive, matching how similar operations already behave.
2026-07-06 12:36:47 +02:00
Stefan Haller 6681ba7eda Show a waiting status while resetting to a ref
Resetting to a commit/branch/tag from the reset menu ran inline on the UI
thread with no spinner; a hard reset to a distant commit can take a while
and blocks the UI meanwhile. Run it on a worker with a waiting status. The
undo/redo callers of ResetToRef already wrap it this way.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:56:07 +02:00
Stefan Haller ccf49c8112 Show a waiting status when editing a commit
Setting a single commit to "edit" ran the interactive rebase inline on the
UI thread with no spinner, while its sibling startInteractiveRebaseWithEdit
(used when editing multiple commits or quick-starting a rebase) already runs
on a worker with a waiting status. Make the direct path match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:55:28 +02:00
Stefan Haller 2bae29c6fd Show a waiting status when starting an interactive rebase onto a ref
The interactive-rebase item in the rebase-onto-ref menu ran inline on the UI
thread with no spinner, unlike its two siblings in the same menu (simple
rebase and rebase onto base branch), which already run on a worker with a
waiting status. Make it consistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:55:12 +02:00
Stefan Haller 539ede2e1b Show a waiting status while merging a branch
The regular and squash merges from the merge menu ran inline on the UI
thread, freezing it with no spinner while git worked. Run them on a worker
with a waiting status, like the rebase entry points already do.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:54:38 +02:00
Stefan Haller 44ba0d539f Show a waiting status while continuing a merge or rebase
Continuing, skipping, or aborting a merge/rebase from the options menu ran
the git command inline on the UI thread, freezing the UI with no spinner
while it worked (a continue can replay many commits). Run the
non-subprocess path on a worker with a waiting status instead, matching how
the other merge/rebase entry points already behave.

The auto-skip recursion in CheckMergeOrRebaseWithRefreshOptions must not
start its own worker: it already runs on the caller's thread (the worker of
the enclosing waiting status, or the UI thread for the synchronous callers).
Route it through genericMergeCommandImpl with the waiting status suppressed
so its behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:54:14 +02:00
Stefan Haller ce5a8b61bd
Update docs and schema for release (#5761)
Some checks failed
Generate Sponsors README / deploy (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Has been cancelled
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Has been cancelled
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Has been cancelled
Continuous Integration / build (push) Has been cancelled
Continuous Integration / check-codebase (push) Has been cancelled
Continuous Integration / lint (push) Has been cancelled
Continuous Integration / check-for-fixups (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Continuous Integration / upload-coverage (push) Has been cancelled
2026-07-04 14:43:54 +02:00
Stefan Haller 440f357319 Update docs and schema for release 2026-07-04 14:40:33 +02:00
Stefan Haller 26dd82d8d4
Update translations from Crowdin (#5760) 2026-07-04 14:39:50 +02:00
Stefan Haller 8e36fba91e Update translations from Crowdin 2026-07-04 14:36:49 +02:00
Stefan Haller 24c6d38983
Show renamed files in the custom patch builder (#5759)
When showing the files of a commit, we used to display renamed files as
a pair of added and deleted files, rather than a single `R` entry. This
is inconvenient when just browsing the commit's files because you can't
see if the rename also has modifications; but it becomes a real problem
when trying to work with the renamed file in a custom patch if it also
had modifications; there was no way to discard them, for example.

The Files panel already shows renamed files as `R` and allows you to
stage/unstage/discard hunks in them, so there's no reason why the patch
building panel shouldn't allow the same; and this PR adds this.

To drop just the modifications of a rename, add the individual hunks to
the custom patch (the side panel shows a `◐` icon); reverting the patch
then only drops the modifications but not the rename. To also drop the
rename, add the entire `R` file to the custom patch from the side panel
(it gets a `●` icon).
2026-07-04 13:07:55 +02:00
Stefan Haller aa46a69f77 Remove unused function ExpectClipboard
This can't be used because it wouldn't work on CI; delete it so that
coding agents aren't tempted to use it.
2026-07-04 13:05:09 +02:00
Stefan Haller f84ada4941 Show renamed files in the custom patch builder
When loading the files of a commit we passed --no-renames, so a rename
showed up as a separate delete and add rather than a single R entry.
That made it impossible to work with a rename that also modifies the
file: the modifications were spread across a full deletion and a full
addition instead of appearing as the handful of lines that actually
changed. The staging view already shows renames and lets you stage
their hunks, so there was no good reason for the patch builder to
differ; the flag was only there because the commit-file parser couldn't
cope with the rename record format.

Switch the commit-file loader and the per-file diff to --find-renames,
teach the parser about the rename record (a status followed by two
paths), and carry the previous path through the patch builder so the
diff for a rename is loaded with both paths, which is what makes git
emit the rename in the first place.

A whole-file selection keeps the rename in the header, so the rename
moves or is discarded together with the file's contents. A partial
selection instead strips the rename metadata and points the header at
the new path, so applying the patch only changes the contents and
leaves the rename in place; the blob index line is kept so that a 3-way
apply can still fall back to a blob merge.

Discarding a renamed file from a commit now discards both the new and
the old path, so the new file is removed and the old one is restored.

Changing the rename similarity threshold refreshes the commit files
panel too, not just the files panel, so that a rename can turn into a
delete and add or back. It is disabled while building a patch, however,
because the patch builder caches each file's diff by path and would
desync if a rename changed into a delete and add underneath it.

Finally, copying a file's diff from the commit files panel now passes
both paths for a rename, so the copied diff shows the rename instead of
a new-file add.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 13:05:09 +02:00
Stefan Haller c085598100
Add gui.shrinkSidePanelsToContent option (#5754)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / lint (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
In many cases some of the side panels show a lot of empty space; for
example the branches panel when there is only a `main` branch. This gets
worse when `expandFocusedSidePanel` is on (accordion mode), in which
case the almost-empty panel gets even bigger when focused and steals
valuable space from the other panels that do have something to show.

This PR adds a new `gui.shrinkSidePanelsToContent` option which causes
side panels to never show more than their content (plus one blank line,
so it's clear there's nothing more below). This means that panel heights
are now dynamic and may change as their content changes; for example,
when the working tree is clean the Files panel shows only two blank
lines, but as you start changing file, it gets taller to show them.

The option is off by default because it takes some getting used to. It
is also independent of the `expandFocusedSidePanel` option; that one
just makes the effect even more pronounced.
2026-07-03 19:28:02 +02:00
Stefan Haller a929f34c84 Add gui.shrinkSidePanelsToContent option
Accordion mode expands the focused side panel, but when that panel has
little content (an empty Files panel, a Branches panel with only master)
it just fills the extra height with blank space. The same waste happens
for any panel that gets more height than it has content to show.

When this option is enabled, each side panel is sized to its own content
(plus a blank line, so it's clear there's nothing more below) rather than
to an equal share of the height. The height a small panel gives up flows
to the panels that have more content than fits; those grow up to their
content and then scroll, weighted toward the focused panel in accordion
mode so the two features compose. Only when every panel fits with room to
spare is the leftover shared out equally, regardless of focus: enlarging
the focused panel there would reveal no more content and would only make
the panels jump around as the focus moves.

The option is independent of expandFocusedSidePanel and off by default.
The status panel, and the stash panel when unfocused, keep their fixed
one-line height as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:25:01 +02:00
Stefan Haller b039d98b09 Scale the side-panel layout height thresholds by panel count
The height thresholds that decide between the proportional layout and
the squashed layout (and, within the squashed layout, between 3-row and
1-row unfocused panels) were hard-coded constants tuned for the fixed
set of five side panels. Now that the panels are configurable, a layout
with fewer panels has less to fit, yet was still forced into the
squashed layout at the same height as five panels would be.

Scale the thresholds down in proportion to the panel count so a smaller
layout keeps using the proportional layout at smaller heights. Only ever
scale down: raising the thresholds for more panels would make them
squash sooner, which works against the reason someone adds panels in the
first place (they want to see them).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:25:01 +02:00
Stefan Haller 758b5dde1f
Allow overriding the platform used for default keybindings (#5671)
A handful of default keybindings differ by platform (e.g. word-wise
cursor movement in text inputs uses alt on macOS but ctrl elsewhere).
Lazygit chooses these based on the OS it runs on, but that's the wrong
signal when the OS isn't where the user is actually typing: someone
running lazygit in a Linux container that they access over ssh from a
Mac gets the Linux bindings, when they'd rather have the Mac ones.
Remapping each binding by hand via config is tedious, so add a single
LAZYGIT_KEYBINDING_PLATFORM override.

Closes #5668.
2026-07-03 19:16:15 +02:00
Stefan Haller 9a4ef7d1a1 Allow overriding the platform used for default keybindings
A handful of default keybindings differ by platform (e.g. word-wise
cursor movement in text inputs uses alt on macOS but ctrl elsewhere).
Lazygit chooses these based on the OS it runs on, but that's the wrong
signal when the OS isn't where the user is actually typing: someone
running lazygit in a Linux container that they access over ssh from a
Mac gets the Linux bindings, when they'd rather have the Mac ones.
Remapping each binding by hand via config is tedious, so add a single
LAZYGIT_KEYBINDING_PLATFORM override.

An unrecognized value falls back to the real OS rather than to the
non-darwin default bindings, since the latter would be an arbitrary
choice.
2026-07-03 19:08:22 +02:00
Stefan Haller 0ecced93ea
Improve deleting worktrees and their branches (#5748)
This PR improves three rough edges around deleting worktrees and the
branches checked out in them:

- **Deleting a branch that's checked out in another worktree now
actually deletes the branch.** The menu used to offer to remove or
detach the worktree but then stop there, leaving behind the very branch
you asked to delete. Both actions now delete the branch afterwards, and
the labels say so ("Remove worktree and delete branch" / "Detach
worktree and delete branch"). The old "Switch to worktree" entry is
dropped — it's not a useful option in the context of deleting a branch.

- **A branch's local branch, remote branch, and worktree can be deleted
in one step.** Picking "Delete local and remote branch" for a single
branch that's checked out in another worktree used to fail with a
confusing "select them one by one" error (which only makes sense for a
multi-selection). It now goes through the same worktree menu, with
labels that make clear the remote is deleted too.

- **Pressing `d` on a worktree can delete its branch.** The plain
confirmation becomes a menu: "Remove worktree", "Remove worktree and
delete branch", and "Remove worktree and delete local and remote
branch".

Throughout, the explicit menu choice acts as the confirmation, so the
separate "Are you sure you want to remove worktree?" prompt is gone; the
dirty-worktree force prompt and the not-fully-merged warning still show
up when relevant.

Closes #5205.
2026-07-03 19:07:25 +02:00
Stefan Haller 0aed44c7f3 Offer to delete the branch when removing a worktree
Pressing `d` on a worktree only ever removed the worktree, leaving its branch
behind even though deleting it too is often what you want. Turn the confirmation
into a menu: "Remove worktree", "Remove worktree and delete branch", and "Remove
worktree and delete local and remote branch". The branch-deleting items come
after the plain removal (they do more harm if picked by accident); both are
greyed out for a detached-HEAD worktree, and the local-and-remote one is also
greyed when the branch has no upstream. The plain menu pick is the confirmation,
so the standalone "remove worktree?" prompt is gone (and its now-dead
translation string with it); the dirty-worktree force prompt and the
unmerged-branch warning still appear when relevant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:04:44 +02:00
Stefan Haller 22914da8e5 Allow deleting local+remote of a worktree-checked-out branch at once
Picking "Delete local and remote branch" for a single branch that's checked
out in another worktree used to fail with "Some of the selected branches are
checked out by other worktrees. Select them one by one to delete them." That
message only makes sense for a multi-selection; for a single branch there's no
reason we can't remove the worktree and delete both the local and remote branch
in one go. Route that case through the same worktree menu as the local-only
delete, with labels that spell out that the remote goes too. The multi-select
error stays for actual multi-selections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:04:44 +02:00
Stefan Haller 4f078f5463 Delete the branch when deleting it via its worktree
When you delete a local branch that's checked out in another worktree, the
menu offered to remove or detach the worktree but then stopped there, leaving
the branch you asked to delete still around. Now both actions delete the branch
afterwards, and the labels say so ("Remove worktree and delete branch" /
"Detach worktree and delete branch") to avoid surprises.

Also drop the "Switch to worktree" item: switching abandons the delete the user
asked for, and it's already reachable by checking out the branch or via the
worktrees panel. And drop the now-redundant "remove worktree?" confirmation:
the explicit menu pick is the confirmation (the dirty-worktree force prompt and
the unmerged-branch warning still appear when relevant).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:04:44 +02:00
Stefan Haller 9a8244110f Let worktree removal/detach chain follow-up work
Split the actual worktree removal out of the confirmation in Remove into a
non-confirming helper, and give both Remove and Detach an optional `then`
continuation that runs after a successful removal in place of the default
refresh. Upcoming flows need to delete the worktree's branch once the worktree
is out of the way; threading a continuation through (rather than the caller
firing branch deletion independently) keeps it ordered after the git command
that actually frees the branch. No behavior change yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:04:44 +02:00
Stefan Haller d6016d6286 Extract reusable branch-deletion helpers
Pull the merged-check-and-force-warning step and the actual git deletion
out of ConfirmLocalDelete and ConfirmLocalAndRemoteDelete into helpers, so
that the upcoming worktree-aware delete flows can reuse them instead of
duplicating the logic. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:04:44 +02:00
Stefan Haller 3f6a21f7f8 AGENTS.md additions 2026-07-03 19:04:44 +02:00
Stefan Haller 7458275820
Make creating worktrees simpler and less error-prone (#5741)
Some checks are pending
Continuous Integration / ci - ${{matrix.os}} (~/.cache/go-build, ubuntu-latest) (push) Waiting to run
Continuous Integration / ci - ${{matrix.os}} (~\AppData\Local\go-build, windows-latest) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.32.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.38.2) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (2.44.0) (push) Waiting to run
Continuous Integration / Integration Tests - git ${{matrix.git-version}} (latest) (push) Waiting to run
Continuous Integration / build (push) Waiting to run
Continuous Integration / check-codebase (push) Waiting to run
Continuous Integration / upload-coverage (push) Blocked by required conditions
Continuous Integration / lint (push) Waiting to run
Continuous Integration / check-for-fixups (push) Waiting to run
Codespell / Check for spelling errors (push) Waiting to run
Generate Sponsors README / deploy (push) Waiting to run
Creating a worktree in lazygit used to be more awkward than it needed to
be:

- The first thing you were asked was whether you wanted a "normal" or
"detached" worktree — a distinction that's confusing to face up front,
and meaningless when you're creating a worktree from a commit, tag or
stash (both choices did the same thing there).
- You then had to type the new worktree's path by hand. That's easy to
get wrong (a mistyped `.worktrees`, for instance), it wasn't clear
whether you were meant to enter the parent folder or the full path, and
it was ambiguous what a relative path would resolve against — especially
when you were already inside another worktree.
- If you picked a branch that was already checked out in another
worktree, lazygit still asked you for a path and only told you it
wouldn't work afterwards.
- There was no quick way to create a new branch and a worktree for it
(sharing the same name) in one step.

This PR reworks the whole flow:

- **No more up-front mode menu.** Pressing `w` on a branch, commit, tag,
stash or remote branch opens a menu whose options are tailored to what
you selected, and each option spells out exactly what it will do — e.g.
"New worktree for 'main'", or "New detached worktree at 'v1.2.0'".
- **You never type a path from scratch.** Lazygit offers ready-made
locations — the folders your existing worktrees already live in, plus an
optional configured default — and shows each as a full path before
anything happens. The folder name is filled in for you from the branch
(or the name you choose). You can still pick "Other…" to type a custom
path.
- **Create a branch and its worktree in one step**, with a single name
used for both. Slashes are preserved, so `feature/foo` gives you a
`feature/foo` branch and a matching nested folder.
- **Branches already checked out elsewhere are caught up front:** the
relevant option is greyed out with an explanation of why, instead of
letting you do the work and then failing.
- **The worktrees panel's `n` is now a single branch picker.** Start
typing, or pick from the list: choosing an existing branch checks it out
in a new worktree, choosing a remote branch creates a local tracking
branch for it, and typing a new name creates a new branch off your
current one — each then simply asks where to put the worktree. Branches
that are already checked out are left out of the suggestions.

There's also a new `worktree.defaultPath` setting to tell lazygit where
to create worktrees by default — useful before you have any existing
worktrees for it to take its cue from. Starting that path with `~/` is
supported (your home dir); this is also supported in the "Other…" path
prompt mentioned above.

Fixes #3230
Fixes #4708
Fixes #5664
2026-07-03 19:02:29 +02:00
Stefan Haller 7a67cea687 Expand a leading ~ in worktree paths to the home directory
Lazygit runs git directly rather than through a shell, so a literal "~"
reaches `git worktree add` unexpanded and git creates a directory named
"~" instead of using the home directory.

Expand the tilde ourselves, both for paths typed into the "Other"
location prompt and for the worktree.defaultPath config value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller 737fb98967 Move the new-worktree keybinding from worktrees to universal
The command was renamed from "View worktree options" to "New worktree",
but its keybinding config key was still 'worktrees.viewWorktreeOptions'.
That name no longer matches the command, and the 'worktrees' section made
little sense: it held a single binding that isn't even used in the
worktrees panel (that panel uses universal.new), only in the branches,
remotes, tags, commits, and stash panels. Other keybinding sections are
named after the panel they're local to; this one wasn't local to any.

Move it to universal.newWorktree, which describes the action and drops the
spurious section, and migrate existing configs automatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller d53a9ea854 Add MoveYamlKey helper to move config keys between sections
RenameYamlKey can only rename a key in place, under the same parent. To
migrate a keybinding from one section to another we need to relocate the
key to a different parent mapping, which is a move, not a rename.

MoveYamlKey creates intermediate maps at the destination as needed and
prunes any maps left empty behind the key, so a section that held only
the moved key doesn't linger as an empty mapping in the user's config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller 768d9f1a3f Rework the worktrees-panel 'n' into a branch picker
The old 'n' flow opened a "normal vs detached" menu (the same meaningless
gate the 'w' flow used to have), then asked for a base ref, a path typed from
scratch, and a branch name in three separate prompts.

Replace it with a single picker prompt titled "New worktree for branch",
suggesting local branches not already checked out anywhere, plus remote
branches that don't yet have a local branch of the same name. The entered
value is classified on confirm: an existing local branch checks out into a
new worktree, a remote branch creates a new local tracking branch, and
anything else creates a new branch off the current ref. All three then feed
the same location menu the 'w' flow uses, so paths are chosen from candidates
rather than typed blind. Picking a remote or new branch needs no separate
name prompt — the picker value already is the name. Checked-out branches are
filtered from the suggestions, and a verbatim type-in of one is rejected with
an error.

createWorktree now takes the context to switch focus to once the worktree is
created, so 'n' lands back in the worktrees panel while 'w' still lands in
the branches panel.

This deletes the old NewWorktree / NewWorktreeCheckout core and the now-
orphaned i18n (CreateWorktreeFrom, CreateWorktreeFromDetached, NewWorktreeBase,
NewBranchNameLeaveBlank), completing the migration started for 'w'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller 23d01ac1bd Redesign the 'w' worktree-creation flow
The old flow forced an up-front "normal vs detached" menu (meaningless for
commits, tags and stashes), then asked the user to type a worktree path from
scratch — easy to get wrong, and ambiguous about what relative paths resolve
against. It also offered the same two actions everywhere regardless of what
was selected.

Replace it with per-context "Worktree" menus whose items imply the intent
(new branch + worktree, worktree for an existing branch, detached worktree),
each feeding a shared name -> location -> create pipeline. The location menu
offers candidate parent directories as absolute paths instead of a blank
field, and "Worktree for a branch" is disabled (with a reason) when that
branch is already checked out somewhere, rather than failing after the fact.

Each ref/commit panel binds 'w' in its own controller and calls the matching
typed entry point on the worktree helper, so which menu opens is decided
statically by the call site rather than by dispatching on a ref's dynamic
type. The three commit panels share one menu through BasicCommitsController;
there is no longer a shared worktree-options controller.

The worktrees-panel 'n' flow and its old core (NewWorktree /
NewWorktreeCheckout) are left untouched here so nothing is written and then
rewritten; they migrate, and the dead i18n strings get removed, in a
follow-up commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller b02eca451c Add helper to compute candidate worktree parent directories
This is the core of "never type a path from scratch": from the repo root,
the configured default path, and the parents of existing worktrees, derive
the ordered list of directories under which a new worktree could be placed.
Pure and unit-tested here; wired into the creation flow in a later commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:53:05 +02:00
Stefan Haller ef73406a96 Add worktree.defaultPath config
The redesigned worktree-creation flow never asks the user to type a path
from scratch; instead it offers candidate parent directories. Until a repo
has any linked worktrees to learn from, there's nothing to offer, so let
users seed that list with a configured default location.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:52:09 +02:00
Stefan Haller 16d773fa40
Support custom pagers and passphrase prompts on Windows (#5740)
Custom pagers were not available on Windows because the feature depends
on a working PTY implementation, and the PTY library we are using
doesn't have support for Windows (see
https://github.com/creack/pty/pull/155). This PR adds this support on
our side; the same PTY library is still used on Mac and Linux, but on
Windows we now have our own implementation using ConPTY.

This also enables prompting for SSH passphrases for users who don't have
an ssh-agent running, which previously also didn't work on Windows.
There's one limitation here: pressing `f` in the Files panel means
`fetch --all` by default (unless you turn that off using `git.fetchAll:
false`), in which case passphrase prompting still doesn't work; the
reason is that git spawns a bunch of child processes for each remote to
be fetched, and on Windows these don't inherit the PTY like they do on
Mac and Linux.

Fixes #1453
Fixes #5525
2026-07-03 18:50:05 +02:00
Stefan Haller 20af6ad23c Remove the Windows limitation from Custom_Pagers.md 2026-07-03 18:47:15 +02:00
Stefan Haller 2fce9c91b6 Materialize cursor-forward escapes as space runs
ConPTY compresses runs of default-colored spaces into ECH + CUF
(\x1b[NX\x1b[NC) instead of emitting them literally. ECH is still a
no-op for us — our buffer is built sequentially and has nothing to
erase — but CUF has to materialize as N visible space cells so the
gap actually appears, otherwise content the child wrote with leading
indentation slides left against the preceding cell.

The view's cursorForward branch reuses the same machinery as tab
expansion: substitute the trigger byte for a space and let the
repeatCount path emit the cells under the parser-tracked SGR. The
existing notifyCellsWritten plumbing then advances screenCol over
the gap, keeping subsequent CUP targets aligned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 0c0c50c3f2 Demonstrate that cursor forward escapes collapse runs of spaces
ConPTY compresses runs of default-colored spaces into ECH + CUF
(\x1b[NX\x1b[NC) rather than emitting them literally. Both currently
fall through the parser's swallow path, so the gap they describe
collapses entirely and content that the child wrote with leading
indentation ends up slid left against the previous cell.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 180fe0cd26 Convert forward cursor-positioning escapes into row advances
ConPTY presents its child's output as a screen buffer and uses CUP /
CUD / CNL / VPA to skip over blank rows rather than emitting LFs. The
previous behaviour swallowed all of those and the visible content
collapsed together. Now the escape parser tracks the screen-relative
cursor row, and any CSI that moves the cursor past the current row
emits a cursorDown instruction that the view turns into the matching
number of empty lines.

Column tracking is deliberately omitted: doing it correctly would mean
duplicating the view's grapheme-cluster width math in the parser, and
ConPTY in practice positions to column 1 after a CR-equivalent, which
the existing wx-reset path already handles. ConPTY-internal scrolling
needs no special handling either: it only emits cursor-positioning
escapes within the first, un-scrolled screenful — once its screen
scrolls it switches to plain linefeeds, which the view advances on
directly regardless of the tracked cursor.

Backward cursor moves are silently dropped — the view's buffer is
append-style and can't undo earlier writes. The exception is cursor-home
(CUP to row 1): ConPTY emits it at the start of every screen, so rather
than drop it we re-anchor the row tracking to the current write position.
Without that, a view not rewound in lockstep with ConPTY's screen (the
command log, which streams pty output without a rewind) accumulates
drift, and every later absolute CUP becomes a dropped backward move that
collapses the rows ConPTY positioned with.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 79bc8e0bc6 Demonstrate that cursor positioning escapes collapse blank rows
ConPTY presents its child's stdout as a screen buffer and uses CUP
(`\x1b[<row>;<col>H`) to skip over blank rows rather than emitting LFs
for them. Our escape interpreter swallows CUP via the catch-all
"valid CSI final byte we don't implement" branch, so the blank rows
the child put between non-blank ones disappear and the surrounding
lines collapse together — which is what makes the delta-rendered diff
in the screenshot look like its blank lines and section breaks were
removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 8c035ebe60 Use ConPTY on Windows for pty-backed command execution
The per-platform getCmdHandlerPty split existed because the Unix side
had creack/pty and the Windows side had nothing — so it fell back to a
non-pty handler. Now that oscommands.StartPty provides a pty on both
platforms, the two files collapse into one cross-platform
implementation and the stub is gone.

cmdHandler grows a 'wait' field because the pty path on Windows spawns
via CreateProcess and never runs exec.Cmd.Start — so cmd.Wait wouldn't
work there. Non-pty handlers set wait = cmd.Wait; pty handlers set it
to the wait closure StartPty returns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 1935117141 Add pty support on Windows via ConPTY
Replace the StartPty stub with a real ConPTY implementation:
CreatePipe + CreatePseudoConsole + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+ CreateProcess. Pagers and external diff tools now get real terminal
behavior instead of being handed pipes.

One Windows-specific quirk worth flagging: ConPTY does not EOF the
output pipe when the child exits; conhost keeps it alive until
ClosePseudoConsole is called explicitly. A background waiter goroutine
calls ClosePseudoConsole as soon as proc.Wait returns, so callers see
EOF on outRead — restoring the Unix master-fd-EOFs-when-slave-closes
semantics they depend on.

The ErrPtyUnsupported sentinel and the no-pty fallback in newPtyTask
are gone now that both platforms have a real implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller c85f7530bb Abstract pty startup behind a platform-specific primitive
Move the pty master behind a small interface (Read/Write/Close/Resize),
and push the actual startup into a platform-specific StartPty function
in pkg/commands/oscommands. The Unix implementation still uses
creack/pty; the Windows implementation is a stub that returns
ErrPtyUnsupported, at which point newPtyTask falls back to a plain cmd
task — matching the existing Windows behavior.

The primitive lives in oscommands rather than pkg/gui because the
cmd_obj_runner pty handler (also in oscommands) is going to consume it
too, and tasks → oscommands is the existing dependency direction.

Same observable behavior on every platform; this just carves out a seam
for a real ConPTY implementation on Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 8ec4283e55 Abstract task command over *exec.Cmd
Windows ConPTY can't attach a child process to a pseudoconsole via
os/exec — Go's stdlib doesn't expose PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
(golang/go#62708). The ConPTY path has to call CreateProcess directly,
so it can't hand an *exec.Cmd back to the task runner.

Widen NewCmdTask to accept a small Cmd interface satisfied by both
*exec.Cmd (via the ExecCmd adapter) and the Windows ConPTY command type
we're about to add. Change TerminateProcessGracefully to take
*os.Process, which both cmd shapes can provide.

Behavior is unchanged on every platform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 51c8f9e6ad Always set LAZYGIT_COLUMNS
The env var was previously set only on Windows, where the no-op pty
stub was just running the command without a pty and needed to expose
the width to pager scripts another way. With ConPTY coming to Windows
the rationale disappears there, but the env var is documented in
docs/Custom_Pagers.md for pager scripts that can't query the terminal
width directly. Set it on every platform so those scripts remain
portable, regardless of whether a pty is in play.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-03 18:47:15 +02:00
Stefan Haller 3c04b30336 Have "just lint" show all issues instead of just the first so many 2026-07-03 18:47:15 +02:00
Stefan Haller 2be6ba9391
Auto-dismiss the continue-rebase prompt when it becomes stale (#5758)
When conflicts of an in-progress rebase/merge/cherry-pick/revert are
resolved, lazygit pops up a prompt offering to continue it. This is
helpful when you started the operation in lazygit and resolved the
conflicts in your editor. However, the operation can change out from
under it: a coding agent (or the user in another terminal) might
continue or abort it, or advance it to a commit with new conflicts. The
prompt then becomes stale — pressing continue fails with "no rebase in
progress" or acts on the wrong state.

Track whether the prompt is showing, and on each refresh dismiss it if
the operation is no longer in the "resolved, ready to continue" state
that the prompt is offering to act on.

Also, don't even open the prompt in the first place if the rebase or
merge wasn't started from within lazygit. This covers the case where a
coding agent makes a rebase, stops at conflicts, resolves them, and then
takes some more time to fix the build or run tests, in which case
lazygit would show the "continue rebase?" prompt, which is confusing and
undesired.

The only legitimate case that I personally encounter where I start a
rebase myself outside of lazygit is something like `git rebase -x "make
test" <base-of-my-branch>`, in which case there can never be conflicts
if it's done in-place.

Closes #5197.
2026-07-03 18:45:33 +02:00
Stefan Haller 90d8ef499c Auto-dismiss the continue-rebase prompt when it becomes stale
The prompt offering to continue a rebase/merge is opened from a refresh
and then left to sit until the user acts on it. But the operation can
change out from under it: a coding agent (or the user in another
terminal) might continue or abort it, or advance it to a commit with new
conflicts. The prompt then becomes stale — pressing continue fails with
"no rebase in progress" or acts on the wrong state.

Track whether the prompt is showing, and on each refresh dismiss it if
the operation is no longer in the "resolved, ready to continue" state
that the prompt is offering to act on. This runs on the same refreshes
that would open it (including the background poll and the refresh on
window focus), so the prompt disappears on its own shortly after the
operation moves on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:30:02 +02:00
Stefan Haller ad507d67f4 Only prompt to continue a rebase/merge if we started it
When conflicts of an in-progress rebase/merge/cherry-pick/revert are
resolved, lazygit pops up a prompt offering to continue it. This is
helpful when you started the operation in lazygit and resolved the
conflicts in your editor. But it's confusing when the operation was
started outside lazygit — e.g. by a coding agent in another terminal
that resolves the conflicts but hasn't continued yet because it's still
running tests or fixing the build. lazygit would then prompt unbidden.

Track whether the in-progress operation was started from within lazygit,
and only show the prompt in that case. We record this right after running
a merge/rebase step (in CheckMergeOrRebaseWithRefreshOptions, the
subprocess branch of genericMergeCommand, and the custom-command
conflict path), and clear it whenever a refresh observes that no
operation is in progress — which also handles an operation that was
finished or aborted externally.

The conflict-resolution tests start their operation by running git
directly (not through lazygit's UI), so they call the new test helper
Common.PretendMergeOrRebaseStartedInLazygit to have lazygit treat the
operation as its own and still get the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:28:07 +02:00
Stefan Haller 3d912f058a
Fix a few flaky tests (#5756)
These started out as a few intermittently-failing integration tests, but
the root cause was real: a handful of places where UI updates
(rendering, model updates, view content) could be applied in a different
order than they were issued, because the order depended on goroutine
scheduling rather than being deterministic. In rare cases that could
make the main view briefly show stale or wrong content — most likely
when moving quickly between items. This was never observed in normal
usage, but caused some integration tests to fail occasionally (both
locally and on CI).

Each commit fixes one of these orderings; see the commit messages for
details.
2026-07-02 17:50:49 +02:00
Stefan Haller 873804a37b Make Gui.Update a synchronous FIFO enqueue
Update spawned a goroutine per call that then sent on the user-events
channel, so multiple Update calls from the same goroutine could be
reordered by the scheduler — the doc comment even admitted "the order in
which the user events will be handled is not guaranteed." That
non-determinism is a latent source of flaky rendering: code that queues a
model update and then a render in source order could see them run in the
opposite order.

Send on the channel directly instead, so same-goroutine calls arrive in
source order. The send is non-blocking and panics on a full channel
rather than blocking (a blocked send from the UI goroutine would deadlock
against itself) or silently reordering; the buffer is sized generously so
this is unreachable in normal use. UpdateAsync is now identical to Update
and unused, so it's removed along with the shared updateAsyncAux helper.
2026-07-02 17:32:01 +02:00
Stefan Haller badf398a94 Run moveMainContextPairToTop before queueing main-view tasks
Copy the outgoing view's content into the target view (the flicker-
prevention step) before queuing the render task, rather than after. The
task writes the fresh content from a worker goroutine, so with the old
order the worker write races the UI-thread copy, and the copy can land
last and clobber the fresh content with stale output.

This is only needed while view writes happen concurrently. Once view
writes are serialized on the UI thread and the view write-mutex goes
away, the synchronous copy always precedes the FIFO-queued write
regardless of order, so the reorder becomes unnecessary. No code comment
is added for it, since that comment would be obsoleted by that work and
likely left behind.
2026-07-02 17:32:01 +02:00
Stefan Haller e53c72be52 Make ViewBufferManager.NewTask respect call order
NewTask was incrementing newTaskID and reading taskID inside the
spawned goroutine, so for two NewTask calls in quick succession the
assignment was determined by goroutine scheduling order rather than
call order. When the goroutines reordered, the first NewTask call
could end up with the higher taskID and "win" the staleness check,
superseding the second call's task even though the caller intended
the second to be the latest.

Worse, the staleness check ran after onNewKey, so a goroutine destined
to bail as stale would still reset the view buffer first, potentially
wiping the winning task's already-written output.

Take newTaskID++ synchronously in NewTask so taskIDs follow call order,
and move the first staleness check ahead of onNewKey so a stale task
doesn't side-effect the view before exiting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-02 17:24:48 +02:00
Stefan Haller f063832dd3
Fix submodule index lock contention (#5755)
Suppress optional locks by default again, except foreground refresh.

Commit d94f2f05 dropped the `GIT_OPTIONAL_LOCKS=0` env var that we used
to set on every git command, and re-added lock suppression only as a
`--no-optional-locks` flag on the background files refresh. The intent
was sound — a foreground `git status` should persist git's refreshed
stat-cache — but the change was too broad: it stopped suppressing
optional locks for every other command too.

The one that bites is the main-view diff. When a folder containing
submodules is selected, we render `git diff --submodule -- <dir>`, and
`--submodule` makes git run `git status` inside each submodule to
describe its "modified" state. That status now grabs the submodule's
index.lock. It runs as a PTY task on its own goroutine, so it races any
submodule-mutating action the user triggers — e.g. resetting a submodule
runs `git -C <submodule> stash`, which then fails with "index.lock: File
exists". This is what made submodule/reset_folder flaky. `git status` is
in fact the only command that takes the optional lock, but the env var
also covered its use inside `git diff --submodule`, inside PTY-run
commands, and inside git's own submodule child processes — none of which
a per-command flag reaches cleanly.

Invert the polarity to match how it worked before d94f2f05: the git
command builder disables optional locks on every command by default, and
the single command that benefits from taking the lock — the foreground
files refresh — opts back in. This restores the original contention
avoidance (including against the user's terminal git) while keeping
d94f2f05's stat-cache-persistence win for the foreground refresh.

This fixes a regression introduced by #5712; labelling this as
"ignore-for-release" because that PR hasn't been released yet.
2026-07-02 17:06:38 +02:00
Stefan Haller 4fcf6ddb5f Revert "Add GlobalArg/GlobalArgIf to GitCommandBuilder"
After the previous commit this is no longer needed.

This reverts commit 94db69f64b.
2026-07-02 16:49:36 +02:00
Stefan Haller ccaa96b29d Suppress optional locks by default again, except foreground refresh
Commit d94f2f05 dropped the GIT_OPTIONAL_LOCKS=0 env var that we used
to set on every git command, and re-added lock suppression only as a
--no-optional-locks flag on the background files refresh. The intent
was sound — a foreground `git status` should persist git's refreshed
stat-cache — but the change was too broad: it stopped suppressing
optional locks for every other command too.

The one that bites is the main-view diff. When a folder containing
submodules is selected, we render `git diff --submodule -- <dir>`, and
`--submodule` makes git run `git status` inside each submodule to
describe its "modified" state. That status now grabs the submodule's
index.lock. It runs as a PTY task on its own goroutine, so it races
any submodule-mutating action the user triggers — e.g. resetting a
submodule runs `git -C <submodule> stash`, which then fails with
"index.lock: File exists". This is what made submodule/reset_folder
flaky. `git status` is in fact the only command that takes the
optional lock, but the env var also covered its use inside `git diff
--submodule`, inside PTY-run commands, and inside git's own submodule
child processes — none of which a per-command flag reaches cleanly.

Invert the polarity to match how it worked before d94f2f05: the git
command builder disables optional locks on every command by default,
and the single command that benefits from taking the lock — the
foreground files refresh — opts back in. This restores the original
contention avoidance (including against the user's terminal git) while
keeping d94f2f05's stat-cache-persistence win for the foreground
refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:05:08 +02:00
Stefan Haller 29b70105c2 Reintroduce the gitCmdObjBuilder wrapper
This reverts commit eb988395e6, which removed the wrapper on the
grounds that it no longer had anything to do but delegate. We're about
to give it a job again: hosting a git-specific default environment
variable on every command. Restore the scaffolding first, as a pure
behaviour-preserving step, so the behaviour change that follows is
minimal.
2026-07-02 15:29:57 +02:00
Stefan Haller 80c899a05c
Pin gofumpt version to 0.9.2 (#5753)
It was quite random which version of gofumpt was used depending on how
you invoked it:
- CI would run it via golangci-lint, which we pin to a version that in
turn pins gofumpt to 0.8.0
- running `just format` would use whatever gofumpt is installed locally
(happened to be 0.9.1 in my case)
- saving in VS Code runs it via gopls, so it uses whatever version of
gopls is installed and whichever gofumpt version it bundles (happened to
be 0.10.0 in my case).

So in my case, saving files in VSCode would suddenly reformat them since
I last updated gopls, because the current 0.10.0 version formats code in
a different way. And I don't like some of its choices; this has been
made less aggressive in gofumpt's master (see
https://github.com/mvdan/gofumpt/issues/74), and I'm waiting for a new
release with that behavior before bumping the dependency to the latest
release; until then, pin to 0.9.2.
2026-07-02 11:19:44 +02:00
Stefan Haller 4b082ed096 Run go mod tidy before go mod vendor
With the previous order, `go mod vendor` populated vendor/ from the
current go.mod, and only then did `go mod tidy` prune it. If tidy
changed go.mod, vendor/ was left matching the pre-tidy state, so a
single run could leave vendor/modules.txt inconsistent with go.mod
(it took a second run to converge). Tidying first settles
go.mod/go.sum, then vendor rebuilds vendor/ to match in one pass.

This applies both to the `vendor` recipe (justfile and Makefile)
and to scripts/bump_lazycore.sh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:16:53 +02:00
Stefan Haller 8af6104454 Check gofumpt formatting with the pinned version in CI and lint
golangci-lint bundles gofumpt v0.8.0, which formats code differently
from the v0.9.2 we pin in go.mod. Enforcing formatting through
golangci-lint may therefore disagree with `just format`.

Remove gofumpt from golangci-lint's formatters and instead run the
pinned `go tool gofumpt` as a standalone check via a new
scripts/gofumpt-check.sh, wired into CI, `just lint`, and `make lint`.
goimports stays in golangci-lint; it's stable across versions and
nothing runs a competing copy of it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:16:53 +02:00
Stefan Haller 9882e4a03e Add gofumpt-tool.sh script and use it in VS Code 2026-07-02 10:41:23 +02:00
Stefan Haller 2d4a4dcdc1 Use go tool gofumpt for make format
This way it always uses our pinned 0.9.2 version.
2026-07-02 10:41:23 +02:00
Stefan Haller 659908195e go get -tool mvdan.cc/gofumpt@v0.9.2 2026-07-02 10:41:23 +02:00
Stefan Haller 21ae632c9b Remove obsolete "errors" step from lint job on CI
When this was added (dafac52a4c), the job used
golangci-lint-action@v2, which had the problem that its step log was
sparse (it mostly produced PR annotations), so re-running `golanci-lint
run` was a workaround to dump readable errors into the console. The
current version of the action no longer has this problem, so we can
remove that fallback (it would conflict with what we are about to do in
this branch).
2026-07-02 10:41:00 +02:00
Stefan Haller 73aec42189 Remove obsolete bump_gocui.sh script
We copied the gocui sources into lazygit a while ago, so we no longer
need this.
2026-07-02 10:05:18 +02:00
Stefan Haller fce9b73856
Bump golang.org/x/net from 0.47.0 to 0.55.0 (#5752)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to
0.55.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7770ec48d0"><code>7770ec4</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="4ece7b612a"><code>4ece7b6</code></a>
html: escape greater-than symbol in doctype identifiers</li>
<li><a
href="08be507abc"><code>08be507</code></a>
html: improve Noah's Ark clause performance</li>
<li><a
href="a8fb2fe4f7"><code>a8fb2fe</code></a>
html: properly render fostered elements in foreign content</li>
<li><a
href="0dc5b7a5f8"><code>0dc5b7a</code></a>
html: properly check namespace in &quot;in body&quot; any other end
tag</li>
<li><a
href="a452f3cc17"><code>a452f3c</code></a>
html: ignore duplicate attributes during tokenization</li>
<li><a
href="f8651996b2"><code>f865199</code></a>
quic: fix appendMaxDataFrame erroneously accumulating sentLimit</li>
<li><a
href="210ed3cb90"><code>210ed3c</code></a>
quic: establish a &quot;happened-before&quot; relationship between
stream write and read</li>
<li><a
href="ad8140e0aa"><code>ad8140e</code></a>
quic: fix buffer slicing when handling overlapping stream data</li>
<li><a
href="23ee2efe81"><code>23ee2ef</code></a>
http2: avoid API changes when built with go1.27</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/net/compare/v0.47.0...v0.55.0">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 08:31:47 +02:00
dependabot[bot] 3094f6621b
Bump golang.org/x/net from 0.47.0 to 0.55.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.47.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 06:29:12 +00:00
Stefan Haller 824d1e4387
Bump golangci/golangci-lint-action from 9.2.0 to 9.3.0 (#5746)
Bumps
[golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action)
from 9.2.0 to 9.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/golangci/golangci-lint-action/releases">golangci/golangci-lint-action's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.0</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<h3>Changes</h3>
<ul>
<li>feat: add no-run-logs-group as experimental option by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1403">golangci/golangci-lint-action#1403</a></li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>build(deps): bump github/codeql-action from 4.35.4 to 4.35.5 in the
github-actions group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1395">golangci/golangci-lint-action#1395</a></li>
<li>build(deps): bump tmp from 0.2.5 to 0.2.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1397">golangci/golangci-lint-action#1397</a></li>
<li>build(deps): bump github/codeql-action from 4.35.5 to 4.36.0 in the
github-actions group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1398">golangci/golangci-lint-action#1398</a></li>
<li>build(deps): bump tmp from 0.2.6 to 0.2.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1399">golangci/golangci-lint-action#1399</a></li>
<li>build(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1400">golangci/golangci-lint-action#1400</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/golangci/golangci-lint-action/compare/v9.2.1...v9.3.0">https://github.com/golangci/golangci-lint-action/compare/v9.2.1...v9.3.0</a></p>
<h2>v9.2.1</h2>
<h2>What's Changed</h2>
<p>IMPORTANT: this is the first immutable release.</p>
<h3>Changes</h3>
<ul>
<li>chore: improve workflows by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1394">golangci/golangci-lint-action#1394</a></li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>build(deps-dev): bump the dev-dependencies group with 3 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1325">golangci/golangci-lint-action#1325</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1326">golangci/golangci-lint-action#1326</a></li>
<li>build(deps): bump the dependencies group with 4 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1327">golangci/golangci-lint-action#1327</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1328">golangci/golangci-lint-action#1328</a></li>
<li>build(deps): bump <code>@​types/node</code> from 25.0.2 to 25.0.3 in
the dependencies group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1329">golangci/golangci-lint-action#1329</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1330">golangci/golangci-lint-action#1330</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1332">golangci/golangci-lint-action#1332</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1333">golangci/golangci-lint-action#1333</a></li>
<li>build(deps): bump the dependencies group with 6 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1334">golangci/golangci-lint-action#1334</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 4 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1335">golangci/golangci-lint-action#1335</a></li>
<li>build(deps): bump the dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1336">golangci/golangci-lint-action#1336</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 3 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1337">golangci/golangci-lint-action#1337</a></li>
<li>build(deps): bump <code>@​types/node</code> from 25.0.9 to 25.0.10
in the dependencies group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1338">golangci/golangci-lint-action#1338</a></li>
<li>build(deps): bump fast-xml-parser from 5.3.3 to 5.3.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1339">golangci/golangci-lint-action#1339</a></li>
<li>build(deps-dev): bump the dev-dependencies group with 2 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1340">golangci/golangci-lint-action#1340</a></li>
<li>build(deps-dev): bump the dev-dependencies group across 1 directory
with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1344">golangci/golangci-lint-action#1344</a></li>
<li>build(deps): bump fast-xml-parser from 5.3.4 to 5.3.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1346">golangci/golangci-lint-action#1346</a></li>
<li>build(deps): bump minimatch by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1348">golangci/golangci-lint-action#1348</a></li>
<li>build(deps): bump minimatch from 3.1.3 to 3.1.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1350">golangci/golangci-lint-action#1350</a></li>
<li>build(deps): bump fast-xml-parser from 5.3.6 to 5.4.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1351">golangci/golangci-lint-action#1351</a></li>
<li>build(deps): bump fast-xml-parser from 5.4.1 to 5.5.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1357">golangci/golangci-lint-action#1357</a></li>
<li>build(deps): bump fast-xml-parser from 5.5.6 to 5.5.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1358">golangci/golangci-lint-action#1358</a></li>
<li>build(deps-dev): bump flatted from 3.3.3 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1359">golangci/golangci-lint-action#1359</a></li>
<li>build(deps): bump picomatch from 4.0.3 to 4.0.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1364">golangci/golangci-lint-action#1364</a></li>
<li>build(deps): bump yaml from 2.8.2 to 2.8.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1365">golangci/golangci-lint-action#1365</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="ba0d7d2ec0"><code>ba0d7d2</code></a>
chore: prepare release v9.3.0</li>
<li><a
href="efd0857dc4"><code>efd0857</code></a>
feat: add no-run-logs-group as experimental option (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1403">#1403</a>)</li>
<li><a
href="ed485de1ff"><code>ed485de</code></a>
build(deps): bump undici from 6.24.0 to 6.27.0</li>
<li><a
href="8872e8d04c"><code>8872e8d</code></a>
build(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1400">#1400</a>)</li>
<li><a
href="b163415b47"><code>b163415</code></a>
build(deps): bump tmp from 0.2.6 to 0.2.7 (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1399">#1399</a>)</li>
<li><a
href="e52a9f8996"><code>e52a9f8</code></a>
build(deps): bump github/codeql-action from 4.35.5 to 4.36.0 in the
github-ac...</li>
<li><a
href="8182aa3494"><code>8182aa3</code></a>
build(deps): bump tmp from 0.2.5 to 0.2.6 (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1397">#1397</a>)</li>
<li><a
href="5403a413de"><code>5403a41</code></a>
build(deps): bump github/codeql-action from 4.35.4 to 4.35.5 in the
github-ac...</li>
<li><a
href="82606bf257"><code>82606bf</code></a>
chore: prepare release v9.2.1</li>
<li><a
href="97c8387e66"><code>97c8387</code></a>
chore: improve workflows (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1394">#1394</a>)</li>
<li>Additional commits viewable in <a
href="1e7e51e771...ba0d7d2ec0">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 08:27:46 +02:00
dependabot[bot] 0e2824baac
Bump golangci/golangci-lint-action from 9.2.0 to 9.3.0
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.2.0 to 9.3.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](1e7e51e771...ba0d7d2ec0)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 06:08:35 +00:00
Stefan Haller 3efae7d905
Bump goreleaser/goreleaser-action from 7.2.2 to 7.2.3 (#5745)
Bumps
[goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action)
from 7.2.2 to 7.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/goreleaser/goreleaser-action/releases">goreleaser/goreleaser-action's
releases</a>.</em></p>
<blockquote>
<h2>v7.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>ci(deps): bump the actions group with 3 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/goreleaser/goreleaser-action/pull/563">goreleaser/goreleaser-action#563</a></li>
<li>chore(deps): bump undici from 6.24.1 to 6.27.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/goreleaser/goreleaser-action/pull/565">goreleaser/goreleaser-action#565</a></li>
<li>build: regenerate dist after undici 6.27.0 bump by <a
href="https://github.com/caarlos0"><code>@​caarlos0</code></a> in <a
href="https://redirect.github.com/goreleaser/goreleaser-action/pull/567">goreleaser/goreleaser-action#567</a></li>
<li>ci: auto-rebuild dist on dependabot PRs by <a
href="https://github.com/caarlos0"><code>@​caarlos0</code></a> in <a
href="https://redirect.github.com/goreleaser/goreleaser-action/pull/568">goreleaser/goreleaser-action#568</a></li>
<li>ci: use a GitHub App token to rebuild dist on dependabot PRs by <a
href="https://github.com/caarlos0"><code>@​caarlos0</code></a> in <a
href="https://redirect.github.com/goreleaser/goreleaser-action/pull/569">goreleaser/goreleaser-action#569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/goreleaser/goreleaser-action/compare/v7.2.2...v7.2.3">https://github.com/goreleaser/goreleaser-action/compare/v7.2.2...v7.2.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f06c13b6b1"><code>f06c13b</code></a>
chore: update dist</li>
<li><a
href="d3934597ca"><code>d393459</code></a>
ci: fix job</li>
<li><a
href="ee731b1700"><code>ee731b1</code></a>
chore: workflow dispatch</li>
<li><a
href="55de448558"><code>55de448</code></a>
chore(deps): bump js-yaml from 4.1.1 to 4.2.0</li>
<li><a
href="a4f614e65e"><code>a4f614e</code></a>
ci: use a GitHub App token to rebuild dist on dependabot PRs (<a
href="https://redirect.github.com/goreleaser/goreleaser-action/issues/569">#569</a>)</li>
<li><a
href="d2d17a6c5d"><code>d2d17a6</code></a>
ci: auto-rebuild dist on dependabot PRs (<a
href="https://redirect.github.com/goreleaser/goreleaser-action/issues/568">#568</a>)</li>
<li><a
href="d13def3a6e"><code>d13def3</code></a>
build: regenerate dist after undici 6.27.0 bump (<a
href="https://redirect.github.com/goreleaser/goreleaser-action/issues/567">#567</a>)</li>
<li><a
href="21549b6bea"><code>21549b6</code></a>
chore(deps): bump undici from 6.24.1 to 6.27.0 (<a
href="https://redirect.github.com/goreleaser/goreleaser-action/issues/565">#565</a>)</li>
<li><a
href="47c416d5e8"><code>47c416d</code></a>
ci(deps): bump the actions group with 3 updates (<a
href="https://redirect.github.com/goreleaser/goreleaser-action/issues/563">#563</a>)</li>
<li>See full diff in <a
href="5daf1e915a...f06c13b6b1">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 08:07:32 +02:00
dependabot[bot] 22810c920e
Bump goreleaser/goreleaser-action from 7.2.2 to 7.2.3
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 7.2.2 to 7.2.3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](5daf1e915a...f06c13b6b1)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 05:29:20 +00:00
Stefan Haller a7e79c0c96
Bump actions/checkout from 6 to 7 (#5723)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to
7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>block checking out fork pr for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
<li>getting ready for checkout v7 release by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li>
<li>update error wording by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
<h2>v6.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID
is set by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p>
<h2>v6.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update all references from v5 and v4 to v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
<li>Clarify v6 README by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.1">https://github.com/actions/checkout/compare/v6...v6.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9c091bb21b"><code>9c091bb</code></a>
update error wording (<a
href="https://redirect.github.com/actions/checkout/issues/2467">#2467</a>)</li>
<li><a
href="1044a6dea9"><code>1044a6d</code></a>
getting ready for checkout v7 release (<a
href="https://redirect.github.com/actions/checkout/issues/2464">#2464</a>)</li>
<li><a
href="f0282184c7"><code>f028218</code></a>
Bump the minor-npm-dependencies group across 1 directory with 3 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/2462">#2462</a>)</li>
<li><a
href="d914b262ff"><code>d914b26</code></a>
upgrade module to esm and update dependencies (<a
href="https://redirect.github.com/actions/checkout/issues/2463">#2463</a>)</li>
<li><a
href="537c7ef99c"><code>537c7ef</code></a>
Bump <code>@​actions/core</code> and <code>@​actions/tool-cache</code>
and Remove uuid (<a
href="https://redirect.github.com/actions/checkout/issues/2459">#2459</a>)</li>
<li><a
href="130a169078"><code>130a169</code></a>
Bump js-yaml from 4.1.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2461">#2461</a>)</li>
<li><a
href="7d09575332"><code>7d09575</code></a>
Bump flatted from 3.3.1 to 3.4.2 (<a
href="https://redirect.github.com/actions/checkout/issues/2460">#2460</a>)</li>
<li><a
href="0f9f3aa320"><code>0f9f3aa</code></a>
Bump actions/publish-immutable-action (<a
href="https://redirect.github.com/actions/checkout/issues/2458">#2458</a>)</li>
<li><a
href="f9e715a95f"><code>f9e715a</code></a>
block checking out fork pr for pull_request_target and workflow_run (<a
href="https://redirect.github.com/actions/checkout/issues/2454">#2454</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v6...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>
2026-07-02 07:27:31 +02:00
dependabot[bot] 943a4426a1
Bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 05:24:40 +00:00
Stefan Haller 46427c39f1
Bump actions/cache from 5 to 6 (#5722)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/releases">actions/cache's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update packages, migrate to ESM by <a
href="https://github.com/Samirat"><code>@​Samirat</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1760">actions/cache#1760</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v6.0.0">https://github.com/actions/cache/compare/v5...v6.0.0</a></p>
<h2>v5.0.5</h2>
<h2>What's Changed</h2>
<ul>
<li>Update ts-http-runtime dependency by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1747">actions/cache#1747</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.5">https://github.com/actions/cache/compare/v5...v5.0.5</a></p>
<h2>v5.0.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Add release instructions and update maintainer docs by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1696">actions/cache#1696</a></li>
<li>Potential fix for code scanning alert no. 52: Workflow does not
contain permissions by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1697">actions/cache#1697</a></li>
<li>Fix workflow permissions and cleanup workflow names / formatting by
<a href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1699">actions/cache#1699</a></li>
<li>docs: Update examples to use the latest version by <a
href="https://github.com/XZTDean"><code>@​XZTDean</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li>Fix proxy integration tests by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1701">actions/cache#1701</a></li>
<li>Fix cache key in examples.md for bun.lock by <a
href="https://github.com/RyPeck"><code>@​RyPeck</code></a> in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
<li>Update dependencies &amp; patch security vulnerabilities by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/cache/pull/1738">actions/cache#1738</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/XZTDean"><code>@​XZTDean</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1690">actions/cache#1690</a></li>
<li><a href="https://github.com/RyPeck"><code>@​RyPeck</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/cache/pull/1722">actions/cache#1722</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.4">https://github.com/actions/cache/compare/v5...v5.0.4</a></p>
<h2>v5.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/cache/compare/v5...v5.0.3">https://github.com/actions/cache/compare/v5...v5.0.3</a></p>
<h2>v.5.0.2</h2>
<h1>v5.0.2</h1>
<h2>What's Changed</h2>
<p>When creating cache entries, 429s returned from the cache service
will not be retried.</p>
<h2>v5.0.1</h2>
<blockquote>
<p>[!IMPORTANT]
<strong><code>actions/cache@v5</code> runs on the Node.js 24 runtime and
requires a minimum Actions Runner version of
<code>2.327.1</code>.</strong></p>
</blockquote>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's
changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h2>How to prepare a release</h2>
<blockquote>
<p>[!NOTE]
Relevant for maintainers with write access only.</p>
</blockquote>
<ol>
<li>Switch to a new branch from <code>main</code>.</li>
<li>Run <code>npm test</code> to ensure all tests are passing.</li>
<li>Update the version in <a
href="https://github.com/actions/cache/blob/main/package.json"><code>https://github.com/actions/cache/blob/main/package.json</code></a>.</li>
<li>Run <code>npm run build</code> to update the compiled files.</li>
<li>Update this <a
href="https://github.com/actions/cache/blob/main/RELEASES.md"><code>https://github.com/actions/cache/blob/main/RELEASES.md</code></a>
with the new version and changes in the <code>## Changelog</code>
section.</li>
<li>Run <code>licensed cache</code> to update the license report.</li>
<li>Run <code>licensed status</code> and resolve any warnings by
updating the <a
href="https://github.com/actions/cache/blob/main/.licensed.yml"><code>https://github.com/actions/cache/blob/main/.licensed.yml</code></a>
file with the exceptions.</li>
<li>Commit your changes and push your branch upstream.</li>
<li>Open a pull request against <code>main</code> and get it reviewed
and merged.</li>
<li>Draft a new release <a
href="https://github.com/actions/cache/releases">https://github.com/actions/cache/releases</a>
use the same version number used in <code>package.json</code>
<ol>
<li>Create a new tag with the version number.</li>
<li>Auto generate release notes and update them to match the changes you
made in <code>RELEASES.md</code>.</li>
<li>Toggle the set as the latest release option.</li>
<li>Publish the release.</li>
</ol>
</li>
<li>Navigate to <a
href="https://github.com/actions/cache/actions/workflows/release-new-action-version.yml">https://github.com/actions/cache/actions/workflows/release-new-action-version.yml</a>
<ol>
<li>There should be a workflow run queued with the same version
number.</li>
<li>Approve the run to publish the new version and update the major tags
for this action.</li>
</ol>
</li>
</ol>
<h2>Changelog</h2>
<h3>6.1.0</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v6.1.0 to pick up <a
href="https://redirect.github.com/actions/toolkit/pull/2435">actions/toolkit#2435
Handle cache write error due to read-only token</a></li>
<li>Switch redundant &quot;Cache save failed&quot; warning to debug log
in save-only</li>
</ul>
<h3>6.0.0</h3>
<ul>
<li>Updated <code>@actions/cache</code> to ^6.0.1,
<code>@actions/core</code> to ^3.0.1, <code>@actions/exec</code> to
^3.0.0, <code>@actions/io</code> to ^3.0.2</li>
<li>Migrated to ESM module system</li>
<li>Upgraded Jest to v30 and test infrastructure to be ESM
compatible</li>
</ul>
<h3>5.0.4</h3>
<ul>
<li>Bump <code>minimatch</code> to v3.1.5 (fixes ReDoS via globstar
patterns)</li>
<li>Bump <code>undici</code> to v6.24.1 (WebSocket decompression bomb
protection, header validation fixes)</li>
<li>Bump <code>fast-xml-parser</code> to v5.5.6</li>
</ul>
<h3>5.0.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v5.0.5 (Resolves: <a
href="https://github.com/actions/cache/security/dependabot/33">https://github.com/actions/cache/security/dependabot/33</a>)</li>
<li>Bump <code>@actions/core</code> to v2.0.3</li>
</ul>
<h3>5.0.2</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2c8a9bd745"><code>2c8a9bd</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/cache/issues/1760">#1760</a>
from actions/samirat/esm_migration_and_package_update</li>
<li><a
href="e9b91fdc3f"><code>e9b91fd</code></a>
Prettier fixes</li>
<li><a
href="e4884b8ff7"><code>e4884b8</code></a>
Rebuild dist</li>
<li><a
href="10baf0191a"><code>10baf01</code></a>
Fixed licenses</li>
<li><a
href="e39b386c90"><code>e39b386</code></a>
Fix test mock return order</li>
<li><a
href="b692820337"><code>b692820</code></a>
PR feedback</li>
<li><a
href="60749128a4"><code>6074912</code></a>
Rebuild dist bundles as ESM to match type:module</li>
<li><a
href="5a912e8b4a"><code>5a912e8</code></a>
Fix lint and jest issues</li>
<li><a
href="b9bf592b98"><code>b9bf592</code></a>
Update documentation for v6 release</li>
<li><a
href="80f777761d"><code>80f7777</code></a>
Update packages, migrate to ESM</li>
<li>See full diff in <a
href="https://github.com/actions/cache/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>
2026-07-02 07:23:41 +02:00
dependabot[bot] 450c3c51af
Bump actions/cache from 5 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 05:20:45 +00:00
Stefan Haller 37ab23b680
Bump github.com/sahilm/fuzzy from 0.1.2 to 0.1.3 (#5706)
Bumps [github.com/sahilm/fuzzy](https://github.com/sahilm/fuzzy) from
0.1.2 to 0.1.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sahilm/fuzzy/releases">github.com/sahilm/fuzzy's
releases</a>.</em></p>
<blockquote>
<h2>v0.1.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Functions to use Iterators as Source by <a
href="https://github.com/wbrc"><code>@​wbrc</code></a> in <a
href="https://redirect.github.com/sahilm/fuzzy/pull/29">sahilm/fuzzy#29</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/wbrc"><code>@​wbrc</code></a> made their
first contribution in <a
href="https://redirect.github.com/sahilm/fuzzy/pull/29">sahilm/fuzzy#29</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sahilm/fuzzy/compare/v0.1.2...v0.1.3">https://github.com/sahilm/fuzzy/compare/v0.1.2...v0.1.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8aadc77d46"><code>8aadc77</code></a>
Functions to use Iterators as Source (<a
href="https://redirect.github.com/sahilm/fuzzy/issues/29">#29</a>)</li>
<li>See full diff in <a
href="https://github.com/sahilm/fuzzy/compare/v0.1.2...v0.1.3">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 07:19:38 +02:00
dependabot[bot] cfd8919c87
Bump github.com/sahilm/fuzzy from 0.1.2 to 0.1.3
Bumps [github.com/sahilm/fuzzy](https://github.com/sahilm/fuzzy) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/sahilm/fuzzy/releases)
- [Commits](https://github.com/sahilm/fuzzy/compare/v0.1.2...v0.1.3)

---
updated-dependencies:
- dependency-name: github.com/sahilm/fuzzy
  dependency-version: 0.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 05:08:14 +00:00
Stefan Haller db02d48204
Bump golang.org/x/sys from 0.45.0 to 0.46.0 (#5685)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.45.0 to
0.46.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="d58dcfa8a7"><code>d58dcfa</code></a>
unix: add GPIO constants and structs</li>
<li>See full diff in <a
href="https://github.com/golang/sys/compare/v0.45.0...v0.46.0">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 07:06:31 +02:00
dependabot[bot] 12da065d59
Bump golang.org/x/sys from 0.45.0 to 0.46.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.45.0 to 0.46.0.
- [Commits](https://github.com/golang/sys/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 05:03:31 +00:00
Stefan Haller 3631497cde
Bump golang.org/x/sync from 0.20.0 to 0.21.0 (#5684)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.20.0 to
0.21.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5071ed6a9f"><code>5071ed6</code></a>
all: fix some comments to improve readability</li>
<li>See full diff in <a
href="https://github.com/golang/sync/compare/v0.20.0...v0.21.0">compare
view</a></li>
</ul>
</details>
<br />
2026-07-02 07:01:59 +02:00
dependabot[bot] 4ad4f7a0df
Bump golang.org/x/sync from 0.20.0 to 0.21.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/sync/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 04:59:14 +00:00
Stefan Haller 722239abd1
Don't include common ancestor when picking "both" for a conflict in diff3 style (#5747)
When a merge conflict is rendered in the diff3 style, git includes the
common-ancestor section between the two sides. Pressing `b` used to pick
all three, which doesn't make sense; it now picks only the current and
incoming hunks.

Fixes #5486.
2026-07-01 10:06:05 +02:00
Stefan Haller ed3f4db4f9 Pick both hunks, not the common ancestor, in diff3 conflicts
`b` on a merge conflict is meant to keep both sides. With the diff3
conflict style git additionally renders the common ancestor between the
two sides, and the old ALL selection kept everything between the
outermost markers, dragging that ancestor into the resolved file.

Rename the selection from ALL to BOTH and restrict it to the top and
bottom hunks so the common base is dropped. Without the diff3 style
there is no ancestor section, so the behaviour there is unchanged.

The user-facing keybinding config was already named pickBothHunks; only
the internal enum, handler, translation and log string still said "all".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:57:26 +02:00
Stefan Haller d79f6c0b38 Add a test for picking both hunks in a diff3 conflict
Pressing `b` on a conflict is meant to pick both sides. With the diff3
conflict style, git also renders the common ancestor between the two
sides, and `b` currently keeps that ancestor section too, which is
wrong: the common base is neither side of the merge and must not end up
in the resolved file.

This test captures the current (buggy) behaviour so the follow-up fix
has a clear before/after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:24:19 +02:00
Stefan Haller 760e9e587b
Fix flaky TestNewCmdTaskInstantStop test (#5743)
TestNewCmdTaskInstantStop is flaky: it closes the stop channel from
within start() and asserts the stopped task touched nothing. But Go's
select picks uniformly at random among ready cases, so when opts.Stop
and a data channel are both ready the loop can pick the data channel,
call beforeStart() (which clears the view) and write the prefix before
bailing. In production a task that's already been superseded thereby
clobbers the output the incoming task is about to render.

Check stop with a non-blocking select before each blocking select, so
the stop signal wins whenever it's already closed (Go has no built-in
priority select; this is the idiomatic substitute). The selects keep
their own stop case for liveness, to unblock when stop closes while
parked waiting for data.
2026-06-30 17:40:06 +02:00
Stefan Haller adaef97ffe Give opts.Stop priority in NewCmdTask's read loop
TestNewCmdTaskInstantStop is flaky: it closes the stop channel from
within start() and asserts the stopped task touched nothing. But Go's
select picks uniformly at random among ready cases, so when opts.Stop
and a data channel are both ready the loop can pick the data channel,
call beforeStart() (which clears the view) and write the prefix before
bailing. In production a task that's already been superseded thereby
clobbers the output the incoming task is about to render.

Check stop with a non-blocking select before each blocking select, so
the stop signal wins whenever it's already closed (Go has no built-in
priority select; this is the idiomatic substitute). The selects keep
their own stop case for liveness, to unblock when stop closes while
parked waiting for data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 17:34:57 +02:00
Stefan Haller e82dbf0fc6 Addition to AGENTS.md 2026-06-30 17:34:57 +02:00
Stefan Haller 62e1622f43
Silently consume unrecognized or malformed escape sequences (#5738)
Lazygit only recognizes a handful of escape sequences (mainly for
colors, erasing to the end of the line, and OSC-8 hyperlinks). It would
render all other ones as literal text in the UI, which doesn't make
sense. This wasn't a problem so far because other sequences tend not to
occur in pager output, but we are going to add ConPTY support for
Windows in a later PR, and ConPTY does emit a lot of those escape
sequences, which would then show up as junk in the UI.

While we're at it, also swallow malformed escape sequences instead of
printing them verbatim.
2026-06-30 09:41:11 +02:00
Stefan Haller 06d2450459 Stop leaking other malformed and unimplemented escape sequences
After the previous commit, the escape interpreter still had five paths
that returned an error from parseOne, which view.go handles by rendering
whatever bytes it had accumulated as literal cells. Each of these is a
case where silently consuming the sequence is strictly better than
leaking garbage.

- ';' as the first CSI byte: '\x1b[;5H' is a valid sequence (row
  defaults to 1) but we errored on the leading ';'.
- Intermediate bytes in CSI ('\x1b[0 q' = DECSCUSR): the sequence ends
  in a final byte we don't implement, so consume and drop.
- Malformed SGR params (empty slot like '\x1b[1;;m'): if outputCSI
  fails mid-parse, reset state instead of re-emitting the sequence.
- OSC 8 that isn't actually OSC 8 ('\x1b]8x...'): treat as an OSC we
  don't understand and skip to its terminator rather than error-
  resetting mid-sequence, which used to leave the rest of the OSC body
  to be printed as text.
- The sanity-check overflow paths (too many params, param too long)
  now switch to a 'discard until final byte' state rather than
  returning the accumulated bytes.

A new stateCSIDiscard centralizes the 'consume bytes until the CSI
final' behavior used by both the intermediate-byte and overflow paths.
errCSITooLong and errOSCParseError are gone with their only callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-30 09:20:15 +02:00
Stefan Haller 31ed34a421 Silently consume unrecognized escape sequences
A text-mode escape interpreter can't do anything meaningful with cursor
positioning, DEC private modes, or terminal resets — but it must still
consume them, not print them as literal text. Before this change, any
sequence outside SGR / EL / OSC-8 errored out of parseOne, and view.go
rendered the unparsed bytes as visible cells. On Windows this would show
up as junk at the start of main-panel output once we add PTY support
using ConPTY, because ConPTY's session-init stream is full of such
sequences.

Three additions to the state machine:

- stateEscape: a single byte in 0x30–0x7E after ESC (e.g. ESC c = RIS)
  is a complete Fs/Fp sequence per ECMA-48; consume and reset.
- stateCSI: accept the DEC private-mode prefix bytes (<, =, >, ?), and
  accept a CSI final byte (0x40–0x7E) immediately after [ as the end of
  a zero-param sequence.
- stateParams: accept any CSI final byte we don't implement as the end
  of the sequence rather than a parse error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-30 09:20:15 +02:00
Stefan Haller 8a8dacca14 Demonstrate that unknown escape sequences leak as literal text
The escape interpreter errors on anything outside the handful of
sequences it understands (SGR, EL, OSC 8 hyperlinks), and view.go then
renders the unparsed bytes as text cells.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-30 09:18:37 +02:00
Stefan Haller b31e572547
Improve resolving non-textual and submodule merge conflicts (#5735)
For a submodule conflict (where the two branches point the submodule at
different commits, resulting in a `UU` status), pressing space would
show a confusing "Nothing to stage…" error, and enter just stepped into
the submodule; neither is helpful for resolving the conflict. Now both
space and enter open a dialog where you can pick the current commit or
the incoming commit, each shown with its head commit subject so you know
what you're choosing. And the main panel explains the conflict and lists
the commits each side added, so you can see how they diverged before
deciding.

This now behaves very similarly to the way you resolve non-textual
conflicts for text files (e.g. `AU` or `UD`). Speaking of which: for
these it was still possible to press space to stage them, which simply
ran `git add`, whose behavior in this case is to keep the file. This is
neither obvious nor symmetric, so we change this to route space to the
conflict picker too, like enter.
2026-06-28 16:47:11 +02:00
Stefan Haller 71a6396275 Extract per-case render helpers from FilesController.GetOnRenderToMain
GetOnRenderToMain had grown to handle five distinct rendering cases inline (no
selection, submodule conflict, inline text conflict, non-textual text conflict,
and the normal working-tree diff), which made it hard to follow. Split each case
into its own method so the function reads as a short dispatcher, and pull the
repeated main-view boilerplate into renderToMainWithTask. Pure refactor; no
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:24:02 +02:00
Stefan Haller 050225ffe6 Show per-side commit logs for submodule conflicts in the main view
When a conflicted submodule is selected, the main view shows the commits
each side added relative to their common ancestor as two indented logs,
labelled current and incoming, so it's clear which commit each side would
resolve to.

The logs aren't truncated (the view scrolls). If a side added no commits
of its own (e.g. it was rewound to an ancestor of the other), its head
commit is shown instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:12:36 +02:00
Stefan Haller afe4d14106 Resolve submodule conflicts through a picker
When both sides of a merge moved a submodule's gitlink, git reports it as "UU".
Pressing space used to fall into the submodule no-op guard and pop the confusing
"Nothing to stage..." error, and enter just entered the submodule, which does
nothing to resolve the superproject conflict.

Treat a conflicted submodule like the other non-textual conflicts: both space
and enter now open a picker offering the two candidate commits, "current" and
"incoming", each labelled with its summary. `git checkout --ours/--theirs` is a
no-op on gitlinks, so we resolve by checking the submodule out at the chosen
commit and staging it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:12:36 +02:00
Stefan Haller 860f89e0c9 Route space to the conflict picker for non-textual conflicts
For a non-textual conflict (e.g. DD/AU/UA/UD/DU), pressing space used to run the
normal stage path, which did something unclear: `git add` happens to resolve the
conflict by keeping the file, but that's neither obvious nor symmetric. Route a
single such file to the same Keep/Delete picker that enter opens, so space and
enter agree.

For a range selection that includes one of these conflicts, staging makes no
sense, so disable it with a toast that points the user at resolving them one at
a time. (Entering a range was already disabled with the standard toast.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 16:12:36 +02:00
Stefan Haller 41d92aac36 Extract a predicate for conflicts that need a resolution dialog
Some merge conflicts can't be resolved by editing markers in the merge view;
they require a dialog that picks one side (the "non-textual" conflicts like
DD/AU/UA/UD/DU). Both `enter` and, soon, `space` need to recognize these, so
pull the test into a shared predicate and rename handleNonInlineConflict to
openConflictResolutionMenu to match.

Restructure EnterFile so the predicate is checked first, ahead of the submodule
and inline-conflict branches. This is its final shape: upcoming commits only add
the submodule case to the predicate, with no further reordering. Behavior is
unchanged here, since the predicate is currently false for submodules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:47:13 +02:00
Stefan Haller de6b6ef906 Addition to AGENTS.md 2026-06-28 15:47:13 +02:00
Stefan Haller bc61978175
Add a global keybinding for editing the config file (#5728)
The status panel already binds the universal edit key to "edit config
file", but that's only reachable while the status panel is focused. Add
a dedicated global binding (alt+shift+c) so the config file can be
opened from anywhere.
2026-06-24 22:14:11 +02:00
Stefan Haller 348224a96e Add a global keybinding for editing the config file
The status panel already binds the universal edit key to "edit config
file", but that's only reachable while the status panel is focused. Add
a dedicated global binding (alt+shift+c) so the config file can be
opened from anywhere.
2026-06-24 22:10:52 +02:00
Stefan Haller f3ea0ab902 Extract editConfig into a shared EditConfigAction
The "edit config file" command is about to gain a second, global
keybinding alongside the existing status-panel one. Moving its body into
an action struct (the convention GlobalController already follows for
all its handlers) lets both controllers delegate to one implementation.
2026-06-24 22:03:57 +02:00
Stefan Haller a8834930a0 Remove unnecessary askForConfigFile indirection 2026-06-24 22:02:55 +02:00
Stefan Haller 9b1acce0fe Remove the "Open config file" command
OpenFile (`o`) is for opening a file as if it was double-clicked in
Finder/Explorer; this is useful for binary files like PNGs, but never
for text files. You want to edit them, and there's `e` for that.
2026-06-24 22:02:52 +02:00
Stefan Haller 6be6c34d2a
Make the side panels configurable (#5702)
Lazygit's left-hand side panels — and the tabs within them — have always
had a fixed arrangement. This adds a `gui.sidePanels` option so you can
lay them out the way you want.

You can:

- Reorder the panels
- Hide panels you don't use
- Regroup which panels share a slot as tabs
- Promote a tab to its own top-level panel — e.g. pull Worktrees out of
the Files panel so it's always visible

#### How it works

`gui.sidePanels` is a top-to-bottom list. Each entry is a list of names
that share one panel as tabs. The default is

```yml
gui:
  sidePanels:
    - [status]
    - [files, worktrees, submodules]
    - [branches, remotes, tags]
    - [commits, reflog]
    - [stash]
```

If you want to promote `worktrees` to a side panel of its own, and never
want to see stashes, use

```yml
gui:
  sidePanels:
    - [status]
    - [files, submodules]
    - [worktrees]
    - [branches, remotes, tags]
    - [commits, reflog]
```
2026-06-23 14:20:55 +02:00
Stefan Haller c6b8220772 Test per-repo side panel config and re-application on repo switch
Exercises the path the live reload relies on: a per-repo lazygit.yml
sets a different side panel layout, and switching between repos
re-applies each one's own layout (the new-repo path for the cloned repo,
the cached-repo path on switching back).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 9aaff61b79 Re-apply the side panel config on a live config reload
When the config file changes and lazygit regains focus it reloads the
config, but the side panel window assignments, default views, tab
strips, and z-order were only ever set up on repo entry, so a changed
sidePanels wouldn't take effect until restart. Re-apply it from the
reload path: reassign windows and default views and restore each panel's
default tab.

The focused panel needs care: resetting it to its default tab would
leave the focused tab hidden behind that default tab, so the panel looks
unfocused even though its tab is selected. Re-focus the current context
so its tab stays shown and highlighted; only when the new config hides
the focused panel entirely do we move focus to the default side panel.
Tab strips are already refreshed via configureViewProperties.
2026-06-23 14:15:18 +02:00
Stefan Haller 2614156b2f Add an IsActiveTab assertion for integration tests
Side panel tabs share a window, so which tab is shown is decided by view
z-order rather than the visibility flag (every tab in a window is
'visible'). Tests had no way to assert which tab is actually drawn in
front, which is distinct from which view has keyboard focus. Expose the
window's top view and add an IsActiveTab assertion built on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 30559f1058 Let integration tests post a focus event
Lazygit reloads changed config files when its terminal window regains
focus, but the test harness had no way to simulate that focus event, so
the live config-reload path was untestable. Add a focus event to the
replayed-events queue and expose it through the GuiDriver as FocusIn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 922861bb36 Clear tab strips on views that are no longer tabs
The tab-assignment loop only ever set a view's tabs; it never cleared them.
That was fine when the groupings were fixed, but with gui.sidePanels a
config reload can turn a tab into a standalone panel, and the old tab strip
would linger on its title. Index the tab strips by view name and assign to
every view, so views that dropped out of a multi-tab panel get their tabs
cleared. No change for a given config; this only matters across a reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 196f820af9 Add integration tests for configuring the side panels
Cover the three things gui.sidePanels enables: reordering the panels
(swapping branches and commits, checked via their jump keys), hiding a
panel (omitting stash, checked by cycling past the last panel and wrapping
to the first), and promoting a tab to its own panel (worktrees becomes a
top-level panel reachable by a jump key, and the files panel's remaining
tabs cycle straight to submodules).

The tests drive focus with explicit jump keys rather than ViewDriver.Focus,
which assumes the default panel layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller ba0f7e8dba Show each panel's first configured tab by default
Within a window the visible tab is whichever view sits on top in the
z-order, and onRepoViewReset establishes that z-order from a fixed list
that needn't agree with the configured tab order. After ordering the
views, bring each panel's first configured tab to the top so that, for a
panel whose tabs have been reordered, the configured first tab is the one
shown before the panel is focused. No effect on the default layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller e396483deb Scale the minimum window height with the panel count
In squashed mode (short terminals) the unfocused side panels each reserve
a row and the focused panel takes whatever is left, so once the unfocused
panels' rows fill the height the focused panel collapses to nothing and
panels below it render off-screen. The fixed floor of 9 was tuned for five
panels; with the panel count now configurable (and promotion allowing up
to ten), grow the floor by one per panel so we show the "not enough space"
view instead of a broken layout. Five panels still floor at 9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller da8ef69133 Give the submodules and reflog views standalone titles
These two views only ever appeared as tabs (of the files and commits
panels), so unlike the other side views they had no title set; the tab
strip supplied their label. Once a tab can be promoted to its own panel
they can appear without a tab strip, so set their titles like the others.
This has no effect in the default layout, where both are always tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 56f3049af4 Drive the side panel layout from gui.sidePanels
Replace the hard-coded side panel order, tab groupings, and window
assignments with values resolved from the gui.sidePanels config. The panel
order (SideWindows and the layout boxes), the tab strips (viewTabMap), the
per-context window names, each window's default view, and the jump-label
groups all now come from the config rather than from five separate
hard-coded lists.

A panel's window name is the name of its first tab, and panels not listed
in the config get their own window name so their views stay hidden instead
of overlapping a visible panel. Three small lookups translate config names
into views, tab titles, and contexts; a test keeps them in sync with the
set of valid names. The lookups are split this way (rather than one
resolver) because configureViewProperties runs before the context tree
exists, so the title/view lookups must not depend on it.

The config is applied to a repo's contexts via applySidePanelConfig on
every repo entry, including the cached-repo path: a repo's per-repo config
can differ from the previously visited one's, so each repo's contexts must
be (re)assigned from its own config rather than kept from when they were
first built.

With the default config this reproduces today's layout exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 2f3ed7e0eb Stop requiring jumpToBlock to have exactly five entries
The number of side panels is about to become configurable, so a fixed
count of jump-to-panel keys no longer makes sense: a user who configures
six panels shouldn't be forced to also extend jumpToBlock, and one who
hides a panel shouldn't have to trim it. Drop the count check entirely
(individual keys are still validated) and assign keys to panels
positionally, for as many panels as there are keys. Surplus panels go
without a jump key but remain reachable via the next/previous-panel keys.
This also removes the log.Fatal that the count check guarded against.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 195b578fc1 Add the gui.sidePanels config option
This adds the user-facing surface for configuring the side panels: their
order, which ones are visible, and how tabs are grouped into panels. Each
entry is either a single panel name or a list of names sharing one panel
as tabs, mirroring how the Keybinding type accepts a scalar or a sequence;
the JSON schema restricts the names to the known set so editors can offer
completion and catch typos. The default reproduces today's layout exactly.

Validation rejects unknown or duplicated names, and requires the files,
branches, and commits panels to always be present: a lot of code focuses
those directly (e.g. after resolving a conflict or popping a stash), so
allowing them to be hidden would let that code focus a hidden panel.

Nothing reads the option yet; the layout still uses the hard-coded order.
Wiring follows in a later commit so the inert surface (and its generated
docs and schema) can be reviewed on its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller b7258958b8 Assign panel jump labels by iterating panel groups
The jump-label prefixes were assigned to each side view by name, twice
(once for the on case, once for off), so the panel-to-views grouping and
the panel order were baked into 28 positional statements. Express the
grouping once as a slice of view groups and loop over it, deriving each
panel's label from its index. The label lookup is now bounds-checked, so
it no longer assumes exactly as many jump bindings as panels. Behavior is
unchanged; this prepares the grouping to come from config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 853f01eb3c Make the remote-branches view follow its parent's window
The remote-branches context is a transient guest that takes over a host
window when you drill into a remote. SubCommits and CommitFiles already
adopt their parent's window via SetWindowName when shown; RemoteBranches
relied instead on its static window name ("branches") matching the remotes
context's window. That assumption only holds while remotes lives in the
branches panel. Adopt the parent's window like the other transient guests
so remote branches render in the right place once panels are configurable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller dc9445014d Drive side-panel layout from a single window list
The three branches of sidePanelChildren each spelled out the five side
windows by name, so the panel order lived in three places and the
status/stash sizing special-cases were tangled into positional literals.
Map each branch over one `windows` slice instead, and fold the
normal-height special-cases (status's fixed height, stash's
collapse-unless-focused) into a single per-window function. Behavior is
unchanged; this isolates the ordering so it can later come from config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 7a60f2de80 Ask agents to surface mid-implementation decisions
Record the working preference that calls which come up during
implementation (and weren't settled in planning) should be raised and
decided together, not made unilaterally and discovered later in the diff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:15:18 +02:00
Stefan Haller 9f6db03fea
Fix quoting of shell commands on Windows (#5704)
Lazygit builds a shell command by interpolating Quote'd arguments into a
template and running the result via `cmd /c`. Several things were wrong
on Windows:

- Quote emitted bash-style `\"…\"` quoting, which cmd.exe doesn't
understand. Making it usable at all previously required a fragile
round-trip through str.ToArgv and re-escaping.
- The assembled command line was handed to `cmd /c` without `/s`, so
cmd's default rules stripped the wrong quotes once the line contained
more than two of them (e.g. a quoted editor path at a location with
spaces, plus a quoted filename that also contains spaces).
- Shell metacharacters were escaped with `^` (`&` → `^&`, etc.), which
neutralised command chaining, pipes, redirection and `%VAR%` expansion
in custom commands.

Quote now emits the standard Windows convention directly, and NewShell
hands cmd.exe the fully-assembled line verbatim via SysProcAttr.CmdLine,
wrapped as `cmd /s /c "<command>"`. The /s flag strips exactly the outer
quote pair we add, leaving each argument's own quoting intact. With the
`^` escaping gone, metacharacters in a custom command reach cmd as the
author intended; this also removes the spurious `^` reported in #3092.

Fixes #5560
Fixes #2427
Fixes #4147
2026-06-23 14:14:07 +02:00
Stefan Haller 13817665aa Add end-to-end test for shell command quoting on Windows
The unit tests only assert the arguments lazygit constructs; they can't
catch cmd.exe's own quote-stripping, which is where #5560 actually
manifested. This test builds a small editor executable, places it and
the file it opens at paths containing spaces, runs it through real
cmd.exe via NewShell, and checks the editor received the intended args.
It runs only on Windows.

Co-Authored-By: Antoine Gaudreau Simard <a.simard@multidev.net>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller 6b311ccb62 Fix quoting of shell commands on Windows
lazygit builds a shell command by interpolating Quote'd arguments into a
template and running the result via `cmd /c`. Several things were wrong
on Windows:

- Quote emitted bash-style `\"…\"` quoting, which cmd.exe doesn't
  understand. Making it usable at all previously required a fragile
  round-trip through str.ToArgv and re-escaping.
- The assembled command line was handed to `cmd /c` without `/s`, so
  cmd's default rules stripped the wrong quotes once the line contained
  more than two of them (e.g. a quoted editor path at a location with
  spaces, plus a quoted filename that also contains spaces).
- Shell metacharacters were escaped with `^` (`&` → `^&`, etc.), which
  neutralised command chaining, pipes, redirection and `%VAR%` expansion
  in custom commands.

Quote now emits the standard Windows convention directly, and NewShell
hands cmd.exe the fully-assembled line verbatim via SysProcAttr.CmdLine,
wrapped as `cmd /s /c "<command>"`. The /s flag strips exactly the outer
quote pair we add, leaving each argument's own quoting intact. With the
`^` escaping gone, metacharacters in a custom command reach cmd as the
author intended; this also removes the spurious `^` reported in #3092.

Fixes #5560
Fixes #2427
Fixes #4147

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller e0fcdf1c3f Demonstrate that shell metacharacters are mangled on Windows
On Windows, NewShell escapes shell metacharacters (`&`, `|`, `<`, `>`,
`%`) with `^` and splits the command into separate arguments. The
operators in a custom command therefore never reach cmd as operators,
so command chaining (`&&`), pipes, redirection and `%VAR%` expansion all
silently break (#2427, #4147, #5113; the stray `^` is also what #3092
reports).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller 41efc9a37a Demonstrate that Quote produces invalid Windows quoting
On Windows, Quote wraps arguments in bash-style `\"…\"` and rewrites
embedded double quotes as `"'"'"`. Neither convention is understood by
cmd.exe or CommandLineToArgvW, so commands built from quoted arguments
are mis-parsed once they contain quotes or spaces (#5560).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 14:11:00 +02:00
Stefan Haller f103cf26dd
Restructure the just recipes for running integration tests (#5720)
`just e2e` used to be the *visible-UI* test runner. Running it with no
arguments launched every integration test in a visible UI — painfully
slow — and it was easy to type `just e2e` out of habit when you actually
meant `just e2e-all` (the headless run-all). This PR reshuffles the
recipes so the common, fast path is the one with the shortest name.

## Recipe changes

| Command | Before | After |
| --- | --- | --- |
| `just e2e` | All tests, **visible UI** (very slow) | All tests,
**headless** (what CI runs) |
| `just e2e <name>` | — (no such target) | That one test, **headless** |
| `just e2e-cli [--slow\|--sandbox\|--debug] <name>` | — | A test in a
**visible UI** (the old `e2e`) |
| `just e2e-tui` | unchanged | unchanged |
| `just e2e-all` | All tests, headless | **removed** (now redundant) |

`just e2e` and `just e2e-cli` accept either a test name
(`submodule/reset`) or a full file path
(`pkg/integration/tests/submodule/reset.go`), and `e2e-cli`/`e2e-tui`
mirror the two `cmd/integration_test/main.go` subcommands (`cli` and
`tui`).

Also added zsh tab-completion for the test names, so `just e2e sub<Tab>`
expands to `just e2e submodule/`, then drills into the tests within.
2026-06-23 13:49:29 +02:00
Stefan Haller 131255ccf0 Use headless mode for the "Run current file integration test" vscode task
For running an integration test just to see if it fails or succeeds,
headless mode is sufficient and actually better, because it works in
small terminals like vscode's bottom panel; the "main.go cli" way of
running tests tends to fail there because the layout renders differently
in such a small window. Headless tests use a fixed window size, so they
don't have this problem. It's also slightly faster.

The other vscode tasks (slow and sandbox) are unchanged, they only make
sense with a visible UI.
2026-06-23 13:31:03 +02:00
Stefan Haller 6064c1091c Remove "Open deprecated test TUI" vscode task
I never use this.
2026-06-23 13:31:03 +02:00
Stefan Haller 23d378ddb9 Use just instead of make in AGENTS.md 2026-06-23 13:31:03 +02:00
Stefan Haller e33b6f9397 Document running the integration tests via the just recipes
The integration README still described the raw `go run cmd/integration_test`
and `go test` invocations, which are easy to get wrong (the headless go-test
command in particular) and don't match how we actually run the tests. Rewrite
the running/debugging/sandbox instructions around the justfile's e2e recipes
instead, and point at the optional zsh completion script. Also switch the
test-list regeneration hint to `just generate`, matching the rest of our docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:31:03 +02:00
Stefan Haller 21f13fc3c7 Add zsh completion for the e2e integration-test recipes
just's completion is clap-dynamic and exposes no hook for completing a
recipe's arguments, so `just e2e <Tab>` couldn't suggest anything. Wrap
just's completer: for the e2e/e2e-cli recipes, complete the test names
found under pkg/integration/tests/, delegating everything else back to
just.

The names are fed to _multi_parts so they complete one "/"-separated
segment at a time — an empty <Tab> offers just the categories, then
drills into the tests within a category — and the .go extension and the
shared helper files are stripped so the candidates are exactly the names
the recipe accepts.

Source it from ~/.zshrc (after compinit) to enable; it's a no-op without
just installed and only activates inside a repo with a justfile and a
pkg/integration/tests/ directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:09:17 +02:00
Stefan Haller 658a66e14b Restructure integration-test just targets
`just e2e` was the visible-UI runner, but it's only useful for a single test
(and even then only with --sandbox/--slow); running it without arguments is far
too slow, yet it was easy to invoke by reflex when `just e2e-all` (run all
headlessly) was meant.

Make `just e2e` the everyday headless runner: no arguments runs the whole
suite (what e2e-all did), and a test name runs just that one headlessly via
`go test -run` — which we had no target for before. The visible-UI runner moves
to `e2e-cli`, pairing with the existing `e2e-tui` (the two main.go subcommands).
e2e-all is now redundant and removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 13:09:05 +02:00
Stefan Haller f3eabb5351
Keep selected commits stable across refreshes (#5717)
With the recently added external change detection, it happens more often
now that we refresh the commits list because an agent made a commit in
the background. In this case, if we keep the selection index the same,
it now points at a different commit, making the main view show a
different commit too, which is confusing and annoying. To fix this,
track the selected commit and range anchor by hash before reloading,
then restore those rows if both hashes still exist. This also allows us
to get rid of some bespoke code that did this for the specific cases of
reverting a commit or cherry-picking commits, because those are now
handled by the generic mechanism.
2026-06-23 09:23:54 +02:00
Stefan Haller 7f96c8ff4f Remove bespoke commit selection restoration when starting a rebase
Starting an interactive rebase (the `edit` command and quick-start) used
to capture the selected commit range by hash before starting the rebase
and restore it afterwards, because new update-ref lines for stacked
branches can shift the commits' positions in the list. The generic
keep-selection-by-hash mechanism now does exactly this for every refresh,
including these, so the bespoke code is redundant.

This relies on the previous commit, which taught the generic matcher to
handle the case where the selected commit turns into a rebase TODO entry
while it's being edited - something the bespoke code handled implicitly
by matching on hash alone.
2026-06-23 09:20:05 +02:00
Stefan Haller b6063cff5b Restore commit selection even when the commit's TODO status changed
When restoring the commit selection after a refresh we match by hash and
TODO status. The TODO status is part of the match so that a commit being
reverted or cherry-picked is matched to the real commit rather than to
the rebase TODO entry that shares its hash.

But a selected commit can also change its TODO status across a refresh:
when starting an interactive rebase that stops to edit it, the real
commit becomes a TODO entry. Fall back to matching by hash alone when
there is no exact match, so the selection is still restored in that case.

The next commit relies on this to remove bespoke selection-restoration
code in the local commits controller that matched by hash alone, which
the generic mechanism otherwise wouldn't fully replace.
2026-06-23 09:20:05 +02:00
Stefan Haller c15ab5db5d Keep selected commits stable across refreshes
With the recently added external change detection, it happens more often
now that we refresh the commits list because an agent made a commit in
the background. In this case, if we keep the selection index the same,
it now points at a different commit, making the main view show a
different commit too, which is confusing and annoying. To fix this,
track the selected commit and range anchor by hash before reloading,
then restore those rows if both hashes still exist. This also allows us
to get rid of some bespoke code that did this for the specific cases of
reverting a commit or cherry-picking commits, because those are now
handled by the generic mechanism.
2026-06-23 09:20:05 +02:00
Stefan Haller 10d2f9f715 Allow GpgHelper to refresh differently on success and failure
Preparation for the next commit, which selects the newly created commit
after a commit succeeds, while leaving the selection alone on failure.
For now success and failure use the same refresh options, so behavior is
unchanged.
2026-06-23 08:43:51 +02:00
Stefan Haller 5d5aa0a865 Cleanup: wrap long parameter lists
This makes the following diff a little easier to read.
2026-06-23 08:43:51 +02:00
Stefan Haller 3f8dc527b5 Cleanup: remove unnecessary if statement 2026-06-23 08:43:51 +02:00
Stefan Haller cfb46f440c Add HeadCommitIdx helper function
Not used yet, we'll need it in the next commit.
2026-06-22 09:16:57 +02:00
Stefan Haller d673a0f3b9 Add tests for IsHeadCommit 2026-06-22 09:03:14 +02:00
Stefan Haller 02be5e74ed Tighten a test expectation
This guards against regressions from the changes that follow. We're
about to add a mechanism that keeps the selection anchored by commit
hash, but we need to make sure that it doesn't take effect here; after a
merge we want to select the newly added merge commit. In the current
state of the code this happens to work because we keep the selection
index the same, which happened to be 0 here; later we will change this
to explicitly select the head commit after the merge.
2026-06-22 09:00:15 +02:00
Stefan Haller 0b78438848
Prevent staging from becoming slower over time (#5712)
In a large repo, when touching (editing) more and more files, staging
hunks in lazygit could become slower and slower over time. Specifically,
this happened when you edited a lot of files and then discarded their
changes again. I have seen cases where staging a hunk began to take
seconds; the fix then was to type `git status` on the command line once,
this made it fast again.

The reason was that lazygit was trying too hard to be a good git
citizen, and used the `GIT_OPTIONAL_LOCKS=0` env var on every git
command it made. The consequence was that it never updated the mod date
cache in git's index file, which caused git to rehash every file whose
mod date doesn't match what it recorded in the index, on every refresh.
Typing `git status` updates that cache, which is why this was a
workaround.

Fix this by using the `GIT_OPTIONAL_LOCKS=0` flag only for refreshes
that are running unattended in the background, i.e. the periodic
autoRefresh and the newly external change detection. For those it is
important because it avoids "cannot lock index" errors for commands that
the user might issue at the same time. All other refreshes are user
initiated and no longer use the flag, which is in line with what `git
status` does, so this keeps performance from deteriorating over time.
2026-06-19 18:31:28 +02:00
Stefan Haller eb988395e6 Remove the now-redundant gitCmdObjBuilder wrapper
The wrapper existed to add a git-specific env var to every command. Now
that that's gone, its New/NewShell/Quote methods just delegated to the
inner builder. The only remaining git-specific behavior — the command
runner — is attached in the constructor via CloneWithNewRunner, which
already returns a complete builder, so we can return that directly and
drop the wrapper struct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:14:24 +02:00
Stefan Haller d94f2f05ac Only pass --no-optional-locks for background status refreshes
We set GIT_OPTIONAL_LOCKS=0 for every git command we run. That env var
only affects `git status`: it tells git not to take the optional lock it
would otherwise use to write the index back after refreshing the cached
stat information. The intent was to avoid contending for index.lock with
git commands the user runs in a terminal.

The downside is that our `git status` never persists the refreshed
stat-cache. So whenever the working tree's cached stat info goes stale
(e.g. editing files and discarding the changes, or a checkout), every
subsequent status re-hashes the affected files to confirm they're clean,
and stays slow until something else writes the index (such as the user
running `git status` in a terminal).

Fix this by only suppressing optional locks for refreshes that run
unattended in the background; foreground refreshes triggered by a user
action now run a plain `git status` that writes the refreshed index back,
just like the command line does. Background refreshes keep passing
--no-optional-locks so they still can't cause lock contention.

RefreshOptions gains a Background flag that the background routines set,
threaded down to the status command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:14:24 +02:00
Stefan Haller 94db69f64b Add GlobalArg/GlobalArgIf to GitCommandBuilder
This can be used to add a git argument that goes before the git
subcommand.
2026-06-19 18:14:24 +02:00
Stefan Haller 7c22c5220c
Detect external repo changes via background polling (#5662)
Lazygit refreshes its UI when its terminal window gets the focus, which
is enough for the situation where the user makes a commit in their IDE
or in another git client. It isn't enough for the case that a coding
agent makes commits in the background. Improve this by doing a
light-weight poll of the git state in the background, and refresh when a
change is detected.

This adds two settings to control the mechanism:

- `git.autoDetectExternalChanges` (default true) is the on/off switch,
parallel to autoFetch/autoRefresh
- `refresher.externalChangeCheckInterval` (default 2 seconds) is the
poll cadence

Addresses #5554.
2026-06-19 18:10:38 +02:00
Stefan Haller 3050303ed2 Detect external ref changes via background polling
Add a 2-second background poll that calls Status.RefsSnapshot and
compares against the snapshot stored at the end of the last refs-
touching refresh. On a diff, trigger a full refresh — same scope as the
focus-in handler, because once we know something changed externally
we can't be sure what (an agent might have created a worktree or
stashed something alongside the commit we detected).

Refresh runs in SYNC mode because goEvery already serializes iterations
via <-done: a slow refresh delays the next tick naturally instead of
letting work stack. The post-refresh hook from the previous commit
updates the snapshot, so in-app commands don't cause the next poll to
spuriously re-fire.

Disabled in the integration test config, like autoRefresh and autoFetch,
because demo replays make repo changes throughout the run; at 2-second
cadence the resulting full refreshes compete with the demo's own
choreography and push some demos past their 40-second timeout.

Also list the two new config keys in checkForChangedConfigsThatDontAutoReload
so a config edit warns the user that lazygit needs a restart.
2026-06-19 18:07:48 +02:00
Stefan Haller c1eeacdfe8 Snapshot refs state before refs-touching refreshes
Add the storage and snapshot-update half of the external-change-detection
mechanism. RefreshHelper now keeps a mutex-protected snapshot string and exposes
accessors for it; Refresh captures a fresh snapshot at the start of any refresh
whose scope set includes COMMITS or BRANCHES.

We capture before reading the git state, not after. Capturing after would let an
external change that lands between the git state read and the snapshot (say, the
next step of a rebase running in another terminal) leave the stored snapshot
newer than what we actually rendered; the poller would then see no difference
and never refresh again, stranding the UI on the intermediate state. Capturing
first keeps the snapshot from running ahead of the render, so if disk moves
during the refresh the next poll catches it.

No reader of the snapshot exists yet — the polling goroutine that consumes it
comes in a later commit. Keeping the snapshot hook in its own commit isolates
the invariant that the snapshot stays in sync with what the UI has observed,
which is what makes the poller's change-detection predicate work across in-app
commands and focus-in refreshes.
2026-06-19 18:07:48 +02:00
Stefan Haller 661df80fe8 Add config options for external change detection
Two settings to control the upcoming background polling mechanism:

- git.autoDetectExternalChanges (default true) is the on/off switch, parallel to
  autoFetch/autoRefresh
- refresher.externalChangeCheckInterval (default 2 seconds) is the poll cadence

Disabling is the bool's job, not a magic 0 interval, matching the existing
convention.

Not yet referenced by any code.
2026-06-19 18:07:48 +02:00
Stefan Haller cb12cb2f6b Add Status.RefsSnapshot
A cheap fingerprint of local branches and HEAD that future code can poll to
detect when refs have moved externally.

Branches come from a porcelain for-each-ref. HEAD is read directly from
.git/HEAD: that avoids spawning a child process and captures the symref-or-hash
distinction we need to tell "detached at X" apart from "on a branch pointing at
X" — they share a commit hash, which is exactly the situation at the end of a
rebase when HEAD reattaches to the branch. The reftable backend doesn't keep a
real .git/HEAD (it writes a fixed stub), so when we see that stub or the file is
unreadable we fall back to porcelain commands, which are backend-agnostic.

Uses DontLog so a future polling caller won't spam the command log. Not yet
wired up to any caller.
2026-06-19 18:07:48 +02:00
Stefan Haller 93bd26b9a9 Centralize scope expansion in Refresh
Several downstream conditions in Refresh() relied on multi-scope predicates to
express "if X is in scope, Y also needs refreshing". This makes it hard to add
new code that needs to ask "does this refresh re-read refs?", because the answer
involves mirroring one of those predicates and keeping them in sync forever.

Expand the co-refreshing relationships once, up front, right after the scope set
is built. The downstream conditions then collapse to single-scope checks against
the (now-expanded) set. Behavior is preserved.

Two of the scattered multi-scope conditions are intentionally left as-is because
they express subsumption rather than co-refresh (one branch already does the
work of another internally — expanding would cause double-refresh), and one
expresses mid-function coupling on a flag set inside the COMMITS/BRANCHES block.
2026-06-19 18:07:48 +02:00
Stefan Haller d81b6d9e1d Log CPU time of external commands in addition to wall-clock time
For certain kinds of performance investigations it is useful to see this, and
doesn't terribly pollute the log, so just do this always.
2026-06-19 18:07:48 +02:00
Stefan Haller 04d62e072b Fix schema minimum for refresh and fetch intervals
The schema annotated refreshInterval and fetchInterval with minimum=0,
but the background routines reject a value of 0 (they require
interval > 0 and otherwise log it as invalid and disable the feature).
So 0 is not actually a valid value; switch to exclusiveMinimum=0 so the
schema matches what the code accepts.
2026-06-19 18:07:48 +02:00
Stefan Haller fcaf1512bd
Fix Files Panel artefacts during rebase commands (#5661)
Several commands (rewording or amending an earlier commit, custom patch
operations, etc.) are implemented by starting an interactive rebase that
stops at a commit, amending it, and continuing. When no conflict occurs,
the user isn't meant to notice a rebase happened at all.

But a background file refresh can fire while the rebase is mid-flight
and render a dirty working copy of whatever the behind-the-scenes rebase
is doing (e.g. applying a custom patch).

To fix this, we pause the background routines for the duration of any
waiting-status operation — exactly the window in which lazygit is
driving the git operation itself and will refresh once at the end. The
boundary is also right for the conflict case: when a rebase stops on a
conflict the operation returns, the pause releases, and background
refreshes resume for the interactive resolution that follows.
2026-06-19 18:06:58 +02:00
Stefan Haller 3cf890b7d7 Pause background refreshes while driving a git operation
Several commands (rewording or amending an earlier commit, custom patch
operations, etc.) are implemented by starting an interactive rebase that stops
at a commit, amending it, and continuing. When no conflict occurs, the user
isn't meant to notice a rebase happened at all.

But a background file refresh can fire while the rebase is mid-flight and render
a dirty working copy of whatever the behind-the-scenes rebase is doing (e.g.
applying a custom patch).

To fix this, we pause the background routines for the duration of any
waiting-status operation — exactly the window in which lazygit is driving the
git operation itself and will refresh once at the end. The boundary is also
right for the conflict case: when a rebase stops on a conflict the operation
returns, the pause releases, and background refreshes resume for the interactive
resolution that follows.
2026-06-19 17:56:48 +02:00
Stefan Haller f9c81b655d Make background-refresh pausing reentrant
Replace the pauseBackgroundRefreshes bool with a count. The single existing
caller (subprocess suspend/resume) is unaffected, but we're about to add a
second, independent reason to pause — lazygit driving a git operation that the
background routines would otherwise catch mid-flight — and the two scopes can
overlap. A bool can't represent "two things both want refreshes paused"; a count
can.
2026-06-19 17:56:48 +02:00
Stefan Haller c81c653c55
Fix coloring of wrapped delta lines (#5711)
When using delta as a custom pager, and the view is so narrow that delta
needs to wrap long lines, only the last segment of such a wrapped line
would have its background color extended to the right edge of the view,
resulting in a block of lines with a frayed right edge, like this:

<img width="623" height="218" alt="Screenshot 2026-06-18 at 17 25 36"
src="https://github.com/user-attachments/assets/346156af-212f-44c3-b8aa-2a66c23ed836"
/>

Fix this by extending the wrapped line's background colors to the right
edge as well, making it look like this:

<img width="623" height="218" alt="Screenshot 2026-06-18 at 17 25 23"
src="https://github.com/user-attachments/assets/9af46ba3-701b-47ce-b38d-bdffaf5182fa"
/>
2026-06-18 17:32:11 +02:00
Stefan Haller 51b409383c Extend the fill background to wrapped tail segments
Tools like delta paint each diff line's background with '\x1b[K' so
the color reaches the right edge. Up to now the '\x1b[K' handler
appended (InnerWidth - cx) explicit padding cells with the fill bg
so rendering picked up the color. That worked for short lines but
silently degraded once content exceeded InnerWidth: the repeat
count went non-positive, no cells were added, and after wrapping
the partial tail segment was left without any cells carrying the
fill color, so draw() fell back to the view's default bg.

Record the fill colors on the source line as optional
trailingFillAttributes. In the '\x1b[K' handler set them (and drop
the padding-cell loop — the metadata covers both the wrap and the
non-wrap cases). In draw(), once per source line, pick the trailing
cell's fg/bg from the metadata if present and otherwise from the
view defaults; then the inner-loop fills past-content cells with
that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:22:31 +02:00
Stefan Haller 9c8a02f901 Remove the '\n' sentinel cell
The sentinel was appended to every \n-terminated line solely so that
draw()'s prevFgColor tracking would reset to default for the trailing
area; without it, an AttrReverse-styled last cell would carry its
rendered bg past the end of the line.

The same prevFgColor mechanism propagated AttrReverse past content on
*unterminated* lines too — which doesn't match real terminal behavior
(try `print '\x1b[7m\x1b[31mfoo'` in a shell: the reverse stops at
the last character) and isn't relied on by anything in lazygit, since
all our writers terminate lines with \n.

Drop the sentinel cell, drop prevFgColor, and just have draw() paint
trailing cells with the view's default fg/bg. The
TestUnterminatedReverseLineExtendsToEdge regression test inverts to
document the new (terminal-matching) behavior, renamed accordingly.
TestWriteString expectations also drop the trailing "" that came
from the sentinel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:22:31 +02:00
Stefan Haller ad8335aaa8 Wrap v.lines cells in a line struct
The cells of a source line will soon need to carry metadata about how
the line was terminated (newline vs filled to edge via \x1b[K). Move
to a struct so there's somewhere to put it; this commit only renames
[][]cell to []line{cells: ...} with no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:22:31 +02:00
Stefan Haller 1788eaa91f Rename lineType to cells
This is to free up the name lineType for something else in the next commit.
2026-06-18 17:22:31 +02:00
Stefan Haller d234d8b358 Remove dead \x00 filtering from string conversion helpers
The four ReplaceAll(str, "\x00", "") calls (and the equivalent
rune-by-rune skip in linesToString) are leftover from when cell.chr
was a rune and \x00 was used as an internal sentinel. With chr now
being a string and no code path writing \x00, the filtering never
strips anything.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:22:31 +02:00
Stefan Haller 6c55823492 Demonstrate broken background fill on wrapped \x1b[K-padded lines
Tools like delta emit each diff line with the bg color set, then
\x1b[K to fill the rest of the row with that bg color. When the
content fits within the view's inner width, gocui's \x1b[K handling
appends explicit padding cells and rendering works. When the content
exceeds the inner width, \x1b[K adds no cells (negative repeat
count), the line is wrapped into multiple segments, and the partial
tail segment's trailing cells fall back to the view default bg
instead of continuing the fill color.

Add a test that drives draw() against a tcell mock terminal and
asserts the current (buggy) trailing background.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:22:31 +02:00
Stefan Haller 2f18db4377 Add regression tests for trailing-fill rendering
The next few commits restructure how the view's draw() decides the
fg/bg of cells past the end of a line's content. Pin down three
existing behaviors first so the restructuring stays a refactor:

- '\n' should reset attributes for the trailing area so a reversed
  final cell doesn't bleed into empty space.
- An unterminated line with AttrReverse on its last cell should
  propagate that to the right edge (otherwise the rendered bg
  abruptly stops at the last character).
- '\x1b[K' on a line that fits within InnerWidth should fill the
  remaining cells with the current bg color.

Introduce a small WithSimulationScreen helper that swaps in a tcell
mock terminal so tests can call view.draw() and inspect rendered
cells via Screen.Get().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 17:21:27 +02:00
Stefan Haller 69004ce208
Some fixes to our infrastructure (#5705)
Minor things, mainly to improve the experience on Windows; see
individual commit messages.
2026-06-16 16:28:41 +02:00
Stefan Haller 643f169be2 Don't include integration tests in "just test" on Windows
This allows running "just check" on Windows, it just doesn't check quite
as much.
2026-06-16 16:21:13 +02:00
Stefan Haller fcce4de6fc Allow running check_script.sh when there are uncommitted changes 2026-06-16 16:21:13 +02:00
Stefan Haller df171722cb Make .go files have LF line endings on Windows
Since gofumpt expects and emits LF even on Windows, this makes it easier for
agents to gofumpt their files.
2026-06-16 16:21:13 +02:00
Stefan Haller 061665726e Fix Windows linter errors
Apparently we don't check Windows-only code for linter errors on CI.
2026-06-16 16:21:13 +02:00
Stefan Haller 8a7bef0975
Improve cycling through multiple pagers (#5678)
Lazygit lets you configure multiple pagers and switch between them with
the `|` key. The changes in this PR improve this for the case that you
have more than two.

- **You can see which pager you switched to.** The notification used to
just say "pager 2 of 3"; now it shows the pager's name, so you no longer
have to remember the order to know where you've landed.

- **You can name your pagers.** By default the name is taken from the
pager command, but you can set your own name in the config. This helps
when two entries run the same command with different options (for
example plain `delta` and `delta --side-by-side`).

- **You can cycle backwards.** Alongside `|`, which moves to the next
pager, the new `\` key moves to the previous one — so you can step back
instead of going all the way around the list to return to one you just
passed. This is especially useful when you have two pagers that you
alternate between often (e.g. `delta` and `delta --side-by-side`), but
also have several others in the list that you use only occasionally.

- **Invalid pager setups are caught early.** If an entry combines
options that can't be used together, lazygit now tells you about it on
startup instead of silently producing a broken diff.
2026-06-07 16:00:08 +02:00
Stefan Haller 8534a05a2e Allow cycling pagers in reverse
With more than a couple of pagers, having to cycle forward through all
of them to reach the previous one (or to back out of an accidental press
of `|`) is tedious. Add a second binding that cycles backward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:51:57 +02:00
Stefan Haller 5a4247b234 Extract onPagerChanged helper from cyclePagers
A reverse-cycle handler is about to need the same re-render-and-toast
logic. Pull it out first so the behavior change that follows only has to
swap the cycle direction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:51:57 +02:00
Stefan Haller 6316094d58 Show pager name in the cycle-pager toast, and let users name pagers
When cycling pagers, "Selected pager 2 of 3" gives no clue which pager
you landed on; with several configured you have to remember the order.
Include the pager's name in the toast instead.

The name is normally derived from the first word of the pager command,
but that isn't always enough: two entries can share a command but differ
in options (e.g. "delta" and "delta --side-by-side"), and an entry may
have no command at all (the default entry, or when using
useExternalDiffGitConfig). So add an optional `name` field that
overrides the derived name.

The message was also hardcoded in English; localize it while we're here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:51:57 +02:00
Stefan Haller 81420ce362 Reject pager entries that combine multiple diff mechanisms
A pager (GIT_PAGER) formats the diff git produces, while
externalDiffCommand and useExternalDiffGitConfig change how git produces
the diff in the first place. They are different pipeline stages, not
alternatives, so combining them on one entry just pipes one through the
other and produces garbled output (e.g. delta trying to parse
difftastic's side-by-side output as a unified diff). The two external
mechanisms likewise conflict, with the explicit command silently
shadowing the git config one. Treat all three as mutually exclusive and
reject configs that set more than one on the same entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:51:56 +02:00
Stefan Haller 9739a43355 Terminate tooltip text with a full stop
This is our general convention for tooltips, it was just forgotten here.
2026-06-07 15:51:56 +02:00
Stefan Haller badb089a86 Add a "just check" command 2026-06-07 15:51:56 +02:00
Stefan Haller 68f3bcf53b
Fix unstaging a submodule with dirty content (#5666)
Staging and unstaging submodules didn't work properly when the submodule
had uncommitted changes of its own (modified or untracked files inside
it). This PR fixes a few related problems:

- **Unstaging a submodule that has both a new commit and uncommitted
changes now works.** Previously, staging such a submodule left it
half-staged, and from there the stage key would only ever try to
re-stage it — there was no way to unstage it again. Now the stage key
toggles it back to unstaged as expected.

- **Trying to stage a submodule that has nothing stageable now explains
why.** When a submodule's only changes are uncommitted content inside it
(with no new commit), there's nothing the parent repository can stage.
Instead of the keypress silently doing nothing (except briefly flashing
the status to staged and then back to unstaged), lazygit now shows an
error explaining that you need to commit inside the submodule first.

- **The stage key (space) and the stage-all key (`a`) now behave
consistently.** All of the above applies equally whether you act on the
submodule directly or use "stage all", and "stage all" no longer gets
stuck or behaves differently from the stage key just because a submodule
with uncommitted changes is present in the list.

Fixes #3641.
2026-06-04 09:16:49 +02:00
Stefan Haller 785c8a712c Explain when a submodule has nothing stageable
A submodule that only has dirty or untracked content (no new commit) can't
be staged from the parent repo, but it still shows up as having unstaged
changes. Pressing stage on it therefore briefly flashed as staged and then
reverted, without explaining why nothing was staged.

Detect this case (via `git submodule status`, where a '+' prefix marks a
stageable commit change) in the shared stage/unstage decision: if the only
thing that looks stageable is such a submodule, don't try to stage it.
Instead unstage if there's anything staged to unstage, so the toggle stays
symmetric; otherwise show an error explaining that there's nothing to stage.

Because the decision is shared, this covers both the stage (space) and
stage-all (a) keybindings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller 8b5cfb0425 Optimistically render unstaging a dirty submodule
This map only feeds the optimistic rendering that makes staging feel
instant; it doesn't affect the eventual status, which git reports after
the refresh. The "MM" entry can never be reached for a regular file: a
file at "MM" has stageable unstaged changes, so pressing space stages it
rather than unstaging, and the unstage path is where this map is used. The
only thing that reaches the unstage path at "MM" is a submodule whose
commit is staged on top of dirty content, so this entry exists purely to
update that submodule instantly instead of waiting for the next git
status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller c46c874442 Also verify stage-all can unstage a dirty submodule
Before the staging decision was unified, the stage (space) and stage-all
(a) keybindings each made their own decision, so a fix to one wouldn't
reach the other. Extend the test to drive the submodule through stage-all
as well, guarding against that asymmetry coming back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller 3f0a7512f8 Fix unstaging a submodule with dirty content
The stage/unstage toggle decides what to do based on whether a node has
unstaged changes: if it does, it stages; otherwise it unstages. For a
submodule this breaks down, because dirty or untracked content inside the
submodule always reports as an unstaged change in the parent repo but can
never be staged from there. Once such a submodule's commit pointer is
staged it sits at "MM", and every subsequent press keeps trying to stage
the unstageable dirty content, so it can never be unstaged.

Treat a submodule's unstaged change as stageable only when its commit
isn't already staged, so that a staged submodule unstages on the next
press regardless of leftover dirty content. Because the decision is now
shared by press and stage-all, this fixes both at once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller 66fe18dd59 Unify the stage/unstage decision for press and stage-all
pressWithLock (acting on the selection) and toggleStagedAllWithLock (acting
on the whole tree) each independently decided whether to stage or unstage,
ran the optimistic update, and logged the action. That duplicated decision
has already drifted: the tracked-files filter was added to press months
before it was applied to stage-all, and fixes to one have repeatedly had to
be chased into the other.

Extract that shared decision into toggleStaged, leaving each caller to
supply only the git commands it runs (per-path for the selection, bulk
add -A / reset for the whole tree — the latter is required because the tree
root node has an empty path, so a per-path stage wouldn't work). This is a
pure refactor: the two callers' decisions were already equivalent, so
behavior is unchanged. It exists so the next change to the staging logic
only has to be made once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller c588c5507c Add a test demonstrating that you can't unstage a dirty submodule
When a submodule has both a new commit (which the parent repo can stage)
and dirty working-tree content (which it can't), staging it lands on a
"MM" status. Pressing space again should unstage it, but instead it tries
to stage the dirty content over and over, so you can never get back to an
unstaged state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:12:09 +02:00
Stefan Haller 38526c9ec4
Add direnv support (#5660)
Supports loading direnv's environment files (`.envrc`) when switching
repos or worktrees, or when entering or exiting submodules.

There's no configuration for this; the functionality is automatically
enabled when direnv is installed.

Closes #3653.
2026-06-04 09:10:50 +02:00
Stefan Haller b76c1072ff Offer direnv .envrc approval from inside lazygit
When a user switches into a repo whose .envrc hasn't been approved with
`direnv allow`, the previous behavior was to drop a "blocked" error
popup and leave the user to fix it externally. That meant opening a
terminal, running `direnv allow`, and then either restarting lazygit or
switching repos and back to refresh the env — easy to get wrong, easy
to forget.

When `direnv export json` exits non-zero, follow up with `direnv status
--json` to ask direnv whether the current directory has a not-yet-
allowed .envrc, and if so, get its path. Then show a confirmation popup
with the .envrc contents inline so the user can read what they're
approving. Confirming runs `direnv allow <path>` and re-runs the load
so the new env reaches subprocesses immediately; cancelling leaves the
env unloaded (the same state as before this commit when direnv refused
to load the .envrc).

Using `direnv status --json` instead of parsing the "is blocked"
stderr line means we rely on direnv's structured output rather than
its human-readable error format, which is more stable across versions
and avoids assumptions about output formatting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 09:05:01 +02:00
Stefan Haller bb8955f2de Load direnv environment when switching repos
When a user opens a repo from the recent-repos menu or jumps between
worktrees inside lazygit, only the env vars present at process startup
reach subprocesses. That breaks pre-commit hooks and other tools whose
dependencies are pulled in by a per-repo .envrc — users were left with
read-only operations because the env their shell would normally load via
direnv never made it into lazygit's git invocations.

Shell out to `direnv export json` after each chdir and apply the JSON
delta via os.Setenv/Unsetenv. direnv tracks the previous load in its own
DIRENV_DIFF env var, so the delta also unloads vars from the old repo
when entering one without a matching .envrc. If direnv isn't on PATH the
call is a no-op, so users who don't use direnv pay nothing and users who
do need no config to opt in. Any stderr direnv emits (loading messages,
"blocked .envrc" errors, etc.) goes to the command log.

The integration test puts a fake direnv on PATH and asserts that a value
it exports reaches a custom command after switching repos. Wiring this
up needed runner.go to support `{{actualPath}}` placeholders in
ExtraEnvVars, mirroring the existing support for ExtraCmdArgs, so the
test can prepend a fixture-relative directory to PATH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 09:05:01 +02:00
Stefan Haller 2601556189 Dedupe the recent-repos fallback in setupRepo
The for-loop here was a verbatim copy of openRecentRepo, so call that instead.
2026-06-04 09:05:01 +02:00
Stefan Haller 685dfc87a4 Cleanup: drop unneeded variable 2026-06-04 09:05:01 +02:00
Stefan Haller d86a49ba3f When RecordCurrentDirectory fails, only log the error
If we return the error here, we don't switch repos, but the chdir
happened already, so this would be an inconsistent state (a lot of
lazygit's code assumes that the current directory is always the worktree
root). Only log the error; failing to record the current directory is
not the end of the world.

Also, it is very unlikely to happen; RecordCurrentDirectory only writes
to a small file, and if this fails, then either there is filesystem
corruption of the disk is full, and in both cases the user likely has
much bigger problems.
2026-06-04 09:05:01 +02:00
722 changed files with 58107 additions and 8931 deletions

2
.gitattributes vendored
View file

@ -1,3 +1,3 @@
*.go text *.go text eol=lf
*.md text eol=lf *.md text eol=lf
*.json text eol=lf *.json text eol=lf

View file

@ -8,7 +8,7 @@ jobs:
check-required-label: check-required-label:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5 - uses: mheap/github-action-required-labels@23e10fde7e062233401931a0eece796cd9bf3177 # v5
with: with:
mode: exactly mode: exactly
count: 1 count: 1

View file

@ -28,9 +28,9 @@ jobs:
GOFLAGS: -mod=vendor GOFLAGS: -mod=vendor
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Test code - name: Test code
@ -53,17 +53,25 @@ jobs:
- 2.38.2 # first version that supports the rebase.updateRefs config - 2.38.2 # first version that supports the rebase.updateRefs config
- 2.44.0 - 2.44.0
- latest # We rely on github to have the latest version installed on their VMs - latest # We rely on github to have the latest version installed on their VMs
race:
- false
# Additionally run the whole suite once under the race detector. Data
# races live in lazygit's own Go code rather than in git, so a single
# git version is enough; use the latest to skip the git-build steps.
include:
- git-version: latest
race: true
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Integration Tests - git ${{matrix.git-version}}" name: "Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }}"
env: env:
GOFLAGS: -mod=vendor GOFLAGS: -mod=vendor
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Restore Git cache - name: Restore Git cache
if: matrix.git-version != 'latest' if: matrix.git-version != 'latest'
id: cache-git-restore id: cache-git-restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v6
with: with:
path: ~/git-${{matrix.git-version}} path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}} key: ${{runner.os}}-git-${{matrix.git-version}}
@ -80,24 +88,35 @@ jobs:
run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
- name: Save Git cache - name: Save Git cache
if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest' if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
uses: actions/cache/save@v5 uses: actions/cache/save@v6
with: with:
path: ~/git-${{matrix.git-version}} path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}} key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Print git version - name: Print git version
run: git --version run: git --version
- name: Test code - name: Test code
env: env:
# See https://go.dev/blog/integration-test-coverage # See https://go.dev/blog/integration-test-coverage. The race variant
LAZYGIT_GOCOVERDIR: /tmp/code_coverage # skips coverage: it's redundant with the non-race latest job and
# would only slow the -race build down further. Leaving the dir unset
# makes run_integration_tests.sh take its non-coverage path.
LAZYGIT_GOCOVERDIR: ${{ !matrix.race && '/tmp/code_coverage' || '' }}
# Only set for the race variant. The race detector needs cgo; it's on
# by default on the Linux runner, but we set it explicitly to be safe.
LAZYGIT_RACE_DETECTOR: ${{ matrix.race && '1' || '' }}
CGO_ENABLED: ${{ matrix.race && '1' || '' }}
# Append each test's duration to this file; run_integration_tests.sh
# prints the slowest at the end, to spot slow/anomalous tests.
LAZYGIT_TEST_TIMING: /tmp/test_timings.txt
run: | run: |
mkdir -p /tmp/code_coverage mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh ./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts - name: Upload code coverage artifacts
if: ${{ !matrix.race }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }} name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
@ -109,9 +128,9 @@ jobs:
GOARCH: amd64 GOARCH: amd64
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Build linux binary - name: Build linux binary
@ -136,9 +155,9 @@ jobs:
GOARCH: amd64 GOARCH: amd64
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Check Vendor Directory - name: Check Vendor Directory
@ -162,19 +181,21 @@ jobs:
GOFLAGS: -mod=vendor GOFLAGS: -mod=vendor
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Check formatting
run: ./scripts/gofumpt-check.sh
- name: Lint - name: Lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 # Run even if the formatting check failed, so that both sets of
# problems are reported in a single CI run.
if: ${{ !cancelled() }}
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
with: with:
# If you change this, make sure to also update scripts/golangci-lint-shim.sh # If you change this, make sure to also update scripts/golangci-lint-shim.sh
version: v2.4.0 version: v2.12.2
- name: errors
run: golangci-lint run
if: ${{ failure() }}
upload-coverage: upload-coverage:
# List all jobs that produce coverage files # List all jobs that produce coverage files
needs: [unit-tests, integration-tests] needs: [unit-tests, integration-tests]
@ -182,10 +203,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
@ -221,7 +242,7 @@ jobs:
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}" run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
- name: "Checkout PR branch and all PR commits" - name: "Checkout PR branch and all PR commits"
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }} ref: ${{ github.event.pull_request.head.ref }}

View file

@ -18,7 +18,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Annotate locations with typos - name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@9ba2c57125d4908eade4308f32c4ff814c184633 # v1.2.0 uses: codespell-project/codespell-problem-matcher@9ba2c57125d4908eade4308f32c4ff814c184633 # v1.2.0
- name: Codespell - name: Codespell

View file

@ -13,10 +13,15 @@ on:
description: 'Version bump type' description: 'Version bump type'
type: choice type: choice
required: true required: true
default: 'patch' default: 'minor (normal)'
options: options:
- minor - minor (normal)
- patch - patch (hotfix)
branch:
description: 'Branch to release from'
type: string
required: true
default: 'master'
ignore_blocks: ignore_blocks:
description: 'Ignore blocking PRs/issues' description: 'Ignore blocking PRs/issues'
type: boolean type: boolean
@ -46,15 +51,16 @@ jobs:
fi fi
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v6 uses: actions/checkout@v7
with: with:
repository: jesseduffield/lazygit repository: jesseduffield/lazygit
ref: ${{ inputs.branch }}
token: ${{ secrets.LAZYGIT_RELEASE_PAT }} token: ${{ secrets.LAZYGIT_RELEASE_PAT }}
fetch-depth: 0 fetch-depth: 0
- name: Get Latest Tag - name: Get Latest Tag
run: | run: |
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "v0.0.0") latest_tag=$(git describe --tags --abbrev=0 || echo "v0.0.0")
if ! [[ $latest_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if ! [[ $latest_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Tag format is invalid. Expected format: vX.X.X" echo "Error: Tag format is invalid. Expected format: vX.X.X"
@ -121,7 +127,7 @@ jobs:
IFS='.' read -r major minor patch <<< "$LATEST_TAG" IFS='.' read -r major minor patch <<< "$LATEST_TAG"
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
if [[ "$VERSION_BUMP" == "patch" ]]; then if [[ "$VERSION_BUMP" == "patch (hotfix)" ]]; then
patch=$((patch + 1)) patch=$((patch + 1))
else else
minor=$((minor + 1)) minor=$((minor + 1))
@ -151,15 +157,15 @@ jobs:
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "$NEW_TAG" -a -m "Release $NEW_TAG" git tag "$NEW_TAG" -a -m "Release $NEW_TAG"
git push origin "$NEW_TAG" git push origin "refs/tags/$NEW_TAG"
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v7
with: with:
go-version: 1.25.x go-version: 1.25.x
- name: Run goreleaser - name: Run goreleaser
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2 uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with: with:
distribution: goreleaser distribution: goreleaser
version: v2 version: v2

View file

@ -10,10 +10,10 @@ jobs:
if: ${{ github.repository == 'jesseduffield/lazygit' }} if: ${{ github.repository == 'jesseduffield/lazygit' }}
steps: steps:
- name: Checkout 🛎️ - name: Checkout 🛎️
uses: actions/checkout@v6 uses: actions/checkout@v7
- name: Generate Sponsors 💖 - name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@2fd9142e765f755780202122261dc85e78459405 # v1.6.0 uses: JamesIves/github-sponsors-readme-action@02650b8cd445fc16dfef73195f9c406dce041623 # v1.6.1
with: with:
token: ${{ secrets.SPONSORS_TOKEN }} token: ${{ secrets.SPONSORS_TOKEN }}
file: "README.md" file: "README.md"

View file

@ -1,6 +1,10 @@
version: "2" version: "2"
run: run:
go: "1.25" go: "1.25"
issues:
max-issues-per-linter: 0
max-same-issues: 0
uniq-by-line: false
linters: linters:
enable: enable:
- copyloopvar - copyloopvar
@ -95,14 +99,14 @@ linters:
generated: lax generated: lax
presets: presets:
- comments - comments
- common-false-positives
- legacy
- std-error-handling - std-error-handling
paths: paths:
- vendor/ - vendor/
formatters: formatters:
enable: enable:
- gofumpt # gofumpt is intentionally not listed here: golangci-lint bundles its own
# gofumpt version, which drifts from the one we pin in go.mod. We run that
# pinned version separately via scripts/gofumpt-check.sh instead.
- goimports - goimports
exclusions: exclusions:
generated: lax generated: lax

View file

@ -1,6 +1,6 @@
{ {
"gopls": { "gopls": {
"formatting.gofumpt": true, "formatting.gofumpt": false,
"ui.diagnostic.staticcheck": true, "ui.diagnostic.staticcheck": true,
"ui.diagnostic.analyses": { "ui.diagnostic.analyses": {
// This list must match the one in .golangci.yml // This list must match the one in .golangci.yml
@ -24,6 +24,8 @@
}, },
"go.alternateTools": { "go.alternateTools": {
"golangci-lint-v2": "${workspaceFolder}/scripts/golangci-lint-shim.sh", "golangci-lint-v2": "${workspaceFolder}/scripts/golangci-lint-shim.sh",
"customFormatter": "${workspaceFolder}/scripts/gofumpt-tool.sh",
}, },
"go.lintTool": "golangci-lint-v2", "go.lintTool": "golangci-lint-v2",
"go.formatTool": "custom",
} }

14
.vscode/tasks.json vendored
View file

@ -24,7 +24,7 @@
{ {
"label": "Run current file integration test", "label": "Run current file integration test",
"type": "shell", "type": "shell",
"command": "go run cmd/integration_test/main.go cli ${relativeFile}", "command": "just e2e ${relativeFile}",
"problemMatcher": [], "problemMatcher": [],
"group": { "group": {
"kind": "test", "kind": "test",
@ -61,18 +61,6 @@
"focus": true "focus": true
} }
}, },
{
"label": "Open deprecated test TUI",
"type": "shell",
"command": "go run pkg/integration/deprecated/cmd/tui/main.go",
"problemMatcher": [],
"group": {
"kind": "test",
},
"presentation": {
"focus": true
}
},
{ {
"label": "Sync tests list", "label": "Sync tests list",
"type": "shell", "type": "shell",

210
AGENTS.md
View file

@ -18,13 +18,32 @@ Windows box has only `just`).
list and the keybinding cheatsheets in `docs-master/keybindings/`). Run this list and the keybinding cheatsheets in `docs-master/keybindings/`). Run this
whenever you add/remove/rename an integration test or change keybindings, and whenever you add/remove/rename an integration test or change keybindings, and
commit the result. CI fails if these are stale. commit the result. CI fails if these are stale.
- `just format``gofumpt -l -w .`. Run before every commit. - `just format``go tool gofumpt -l -w .`. Run before every commit.
- `just build` — build the binary. - `just build` — build the binary.
- `just unit-test``go test ./... -short`. - `just unit-test``go test ./... -short`.
- `just e2e-all` — run all integration tests headlessly (`just e2e <name>` runs a - `just e2e` — run all integration tests headlessly; `just e2e <name>` runs a
single one with a visible UI). single one headlessly too. `just e2e-cli <name>` runs one with a visible UI
(most useful with `--sandbox` or `--slow`).
- `just lint` — run golangci-lint. - `just lint` — run golangci-lint.
## Prefer gopls MCP tools for Go symbol questions
When the gopls MCP tools are available in the session, prefer them over grep
for type-aware questions about Go code: who calls a function or method
(`go_symbol_references`), finding a symbol by fuzzy name (`go_search`), or
inspecting a package's API (`go_package_api`). Method names in this codebase
collide a lot (`draw`, `Show`, `Refresh` exist on several types), and grep
needs manual filtering that gopls doesn't. This includes code under
`vendor/`, which gopls resolves as part of the module build.
Grep remains the right tool for strings, comments, config keys, non-Go
files, and anything textual. Don't adopt the full workflow from
`gopls mcp -instructions` (vulncheck on session start, `go_file_context`
after every file read); that overhead isn't worth it here.
If the tools aren't available in a session, fall back to grep silently —
don't try to install, register, or start the server.
## When to commit ## When to commit
Do not leave completed work uncommitted. Once a logical unit of work is done Do not leave completed work uncommitted. Once a logical unit of work is done
@ -45,8 +64,11 @@ while still being meaningful and self-contained.
- **Every commit must compile and pass all tests.** No "WIP" commits, no - **Every commit must compile and pass all tests.** No "WIP" commits, no
commits that leave the tree broken and rely on a follow-up to fix it. commits that leave the tree broken and rely on a follow-up to fix it.
- **Every commit must be `gofumpt`-formatted.** Run `make format` before - **Every commit must be `gofumpt`-formatted.** Run `just format` before
committing. committing.
- **Every commit must be lint-clean.** Run `just lint` before committing —
don't introduce a lint warning in one commit and rely on a later commit
(or the user) to clean it up.
- **Commit messages explain _why_, not _what_.** The diff already shows what - **Commit messages explain _why_, not _what_.** The diff already shows what
changed; the message should capture the motivation, the constraint, or the changed; the message should capture the motivation, the constraint, or the
bug being fixed. If the reason is obvious from a one-line subject, no body bug being fixed. If the reason is obvious from a one-line subject, no body
@ -60,8 +82,26 @@ while still being meaningful and self-contained.
excuse bundling it in. Before committing, review your diff and split out any excuse bundling it in. Before committing, review your diff and split out any
hunk that is behavior-preserving (an extraction, a rename, a move) into a hunk that is behavior-preserving (an extraction, a rename, a move) into a
preceding commit, by staging hunks or resetting and recommitting in order. preceding commit, by staging hunks or resetting and recommitting in order.
- **A preparatory refactor is a new commit only when it prepares something
new.** Before adding one, find the commit that introduced the code you are
about to restructure. If that commit is on this branch, the refactor is a
`fixup!` for it rather than a commit of its own: a branch must never contain
a commit whose code a later commit on the same branch tidies up. A prep
refactor earns a commit of its own only when the shape it corrects came from
before the branch. This holds across a branch stack too — if the commit that
introduced the code is in an earlier branch of the stack, the fixup belongs
there, and the branches above it get replayed. The one exception is when
fixing it there turns out to be unreasonably difficult; ask me what to do
rather than deciding to leave the repair at the tip.
- **Do not use conventional commits** (no `feat:`/`fix:`/`chore:` prefixes). - **Do not use conventional commits** (no `feat:`/`fix:`/`chore:` prefixes).
Match the plain English imperative style of the existing history. Match the plain English imperative style of the existing history.
- **Wrap message body to 72 characters**. The subject is allowed to go up to 80
characters, or even a little more if needed to convey a good single-line
summary; the body should be wrapped at 72 exactly, no more, no less.
- **End every commit message with the `Co-authored-by:` trailer** naming the
model that wrote it, exactly as your harness instructions spell it. Nothing
in `just check` catches a missing one, so it has to be part of writing the
message rather than something to notice afterwards.
## Iterate with `fixup!` commits ## Iterate with `fixup!` commits
@ -80,6 +120,20 @@ separate, reviewable commit that the user decides when to fold in. A bare
`--amend` rewrites the commit on the spot and skips that checkpoint. Don't `--amend` rewrites the commit on the spot and skips that checkpoint. Don't
treat "I'm only touching the tip commit" as an exception. treat "I'm only touching the tip commit" as an exception.
Always use `fixup!` or `amend!` commits, never amend changes directly, even if
you naturally would because "the branch isn't pushed yet". The user always wants
to review what you changed, so make this transparent; no exceptions.
**When the tip is the wrong place for a fixup, insert it mid-branch.**
Committing a fixup at the tip of the branch only works while the code it
touches still looks the same there; once later commits have rewritten that
code — or the target has since been split — the fixup won't apply, and
rewriting the later commits to accommodate it defeats the point. Check out the
target, make the change, `git commit --fixup=<target>`, then
`git rebase --onto <the fixup> <target> <branch>` to replay the rest of the
branch. The fixup stays a separate, reviewable commit; only its position
changes.
If the changes don't map cleanly onto existing commits — say they cut If the changes don't map cleanly onto existing commits — say they cut
across several of them, or restructure something at a different layer across several of them, or restructure something at a different layer
than any existing commit naturally owns — stop and ask the user how to than any existing commit naturally owns — stop and ask the user how to
@ -137,6 +191,32 @@ commit. If you have two independent refinements for the same target, make
two separate fixups. Reviewability of the intermediate state matters even two separate fixups. Reviewability of the intermediate state matters even
when the end state after autosquash would be identical. when the end state after autosquash would be identical.
## Surface mid-implementation decisions; decide them together
Planning can't anticipate everything. When a decision surfaces while you're
implementing — a design choice, a tradeoff, a scope cut, a "this turned out
harder than expected, so maybe X" — don't quietly make the call and keep
going, even if you have a clear recommendation and even if the call seems
small. Stop, lay out the options and your recommendation, and let me weigh in.
I want to make these calls _with_ you, not discover them after the fact in the
diff.
This isn't a request to stop and ask about every trivial detail; obvious
mechanical choices with one sensible answer don't need a checkpoint. It's about
genuine forks — the ones where a reasonable person might pick differently, or
where you'd be trading away something the plan assumed (scope, UX, performance,
reload behavior, …). When in doubt, surface it.
This applies with equal force to unforeseen _discoveries_, not just to
decisions you set out to make. If you find something the plan didn't account
for — a latent bug, a race, a wrong assumption, a case that turns out
unhandled — stop and raise it before designing or writing a fix, even when the
fix seems obvious and even when it's "just correctness." Finding the problem is
itself the fork: whether to fix it here or in a separate change, how generally
to solve it, and whether it reshapes the current work are all calls for me to
make with you. Don't quietly fold a self-directed fix for a newly-found problem
into the branch and let me discover it in the diff.
## Prefer the cleaner design over the smaller diff ## Prefer the cleaner design over the smaller diff
When a task could be implemented either by tacking onto existing code or by When a task could be implemented either by tacking onto existing code or by
@ -162,6 +242,16 @@ that changes the relevant test(s) or adds new ones to demonstrate the bug, then
fix the bug in a follow-up commit. This gives reviewers (and `git bisect`) a fix the bug in a follow-up commit. This gives reviewers (and `git bisect`) a
clear before/after and proves the test actually exercises the broken code path. clear before/after and proves the test actually exercises the broken code path.
This applies only to defects that existed before the entire branch or branch
stack. Never use the bug-demonstration pattern for a regression introduced by
an earlier commit in the current stack. Fix or rewrite the commit that
introduced the regression so that no commit in the final history contains it.
Put the regression test in a preparatory commit before the introducing commit,
so it guards that commit in the final history. If the test cannot pass before
the feature exists, restructure the implementation or test seam until it can;
if that would require a design tradeoff, stop and discuss it rather than adding
a later demonstration/fix pair.
Use the `EXPECTED` / `ACTUAL` pattern in the bug-demonstrating commit. The test Use the `EXPECTED` / `ACTUAL` pattern in the bug-demonstrating commit. The test
asserts the current (wrong) behavior so it passes on the broken code, with the asserts the current (wrong) behavior so it passes on the broken code, with the
correct expectation preserved inline as a comment. The fix commit then swaps correct expectation preserved inline as a comment. The fix commit then swaps
@ -204,7 +294,11 @@ If you find yourself reaching for a local variable so that both forms can be
expressed against the same receiver, the structure isn't right yet — go back expressed against the same receiver, the structure isn't right yet — go back
and fix it instead of papering over it with a binding. and fix it instead of papering over it with a binding.
Use this pattern only where it makes sense; don't apply it by default. Use this pattern only where it makes sense; don't apply it by default. Only
ever use it for bugs, never for added features or behavior changes that aren't
bugfixes; it is useful to demonstrate how a bug existed before fixing it, but
it is never useful to demonstrate how a feature didn't exist before implementing
it.
## Unify duplicated logic before you change it ## Unify duplicated logic before you change it
@ -223,6 +317,34 @@ Follow this even when the need for the refactor is only discovered in the middle
of working on the branch; suggest to the user to rewrite the history to move the of working on the branch; suggest to the user to rewrite the history to move the
refactor to an earlier commit (but don't do it without asking first). refactor to an earlier commit (but don't do it without asking first).
## Don't read model state right after a `Refresh`
A `Refresh` (or `RefreshFromWorker`) does its git work on a worker and then
*enqueues* the model update onto the UI thread. So when `Refresh` returns, the
model is **not** updated yet — the write is still queued. Reading a field
synchronously right after refreshing its scope reads the stale, pre-refresh
value (and this is true even for SYNC refreshes):
```go
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
files := self.c.Model().Files // BUG: still the pre-refresh value
```
Put the read in `RefreshOptions.Then` instead — it's queued after the scope's
model writes, so it sees the fresh value:
```go
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.FILES},
Then: func() error {
files := self.c.Model().Files // fresh
return nil
},
})
```
`Then` is a `func() error` and works with any non-`ASYNC` mode.
## Integration test conventions ## Integration test conventions
Don't bind views to local variables. Always chain method calls directly from Don't bind views to local variables. Always chain method calls directly from
@ -235,6 +357,53 @@ keep the call site fluent.
Prefer `assert.Equal` (and friends) over hand-rolled `if` checks. The failure Prefer `assert.Equal` (and friends) over hand-rolled `if` checks. The failure
messages are more useful and the intent is clearer at a glance. messages are more useful and the intent is clearer at a glance.
## Translatable strings use Go templates, not `%s`
Never put `fmt.Sprintf`-style placeholders (`%s`, `%d`, …) in translatable
strings — the fields of `TranslationSet` and `Actions` in
`pkg/i18n/english.go`. Use named Go-template placeholders and fill them in with
`utils.ResolvePlaceholderString`:
```go
// in english.go
DeleteBranchTitle: "Delete branch '{{.selectedBranchName}}'?",
// at the call site
utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
map[string]string{"selectedBranchName": branchName},
)
```
Named placeholders tell localizers what each value is (a bare `%s` says
nothing, and translators can't safely reorder positional verbs across
languages), and the map form extends cleanly when a string later needs more
than one placeholder. This holds for every user-facing string, including short
ones like disabled-action reasons and toasts.
## Only edit the English translations
`pkg/i18n/english.go` is the one translation file you edit; add, change, and
remove strings there. The other languages under `pkg/i18n/translations/` are
maintained by Crowdin and synced automatically — never edit them by hand, not
even to add a key you just introduced or to delete one you just removed. A
removed English string simply leaves an orphan key in those files, which
Crowdin cleans up on its own; an unknown key in a translation file is ignored
at load time, so it does no harm in the meantime.
## Try to keep new english.go strings within the existing column alignment
`gofumpt` aligns the `TranslationSet` struct fields and the `EnglishTranslationSet`
literal into columns, so a new field whose name is longer than the widest one in
its alignment block re-indents every line in that block. When there are several
feature branches in flight that all add strings, that reformatting churn turns
english.go into a rebase-conflict magnet. So when it's cheap to do so, make an
effort to keep a new field name within the current widest name in the block
(measure it; it's around 40 characters today), shortening the Go field name to
fit. This is a soft preference, not a rule: the usual "best name wins" still
applies, so don't mangle a name past the point of readability just to save a
column. Applies only to `pkg/i18n/english.go`.
## Code comments are for future readers, not development history ## Code comments are for future readers, not development history
Comments in source code explain *why this code is shaped the way it is*. They Comments in source code explain *why this code is shaped the way it is*. They
@ -250,12 +419,32 @@ Avoid phrasings like:
- "cleaner than the previous approach" - "cleaner than the previous approach"
- "we used to ... but ..." - "we used to ... but ..."
- "after trying X, we found Y" - "after trying X, we found Y"
- "X rather than Y", where Y is what the code did before the change
The iteration story is sometimes worth preserving — but it belongs in the The iteration story is sometimes worth preserving — but it belongs in the
commit message, which is the durable record of *why this change was made*. The commit message, which is the durable record of *why this change was made*. The
code comment should make sense to someone who has never seen any prior version code comment should make sense to someone who has never seen any prior version
and is just trying to understand the file as it currently exists. and is just trying to understand the file as it currently exists.
The tell is subtler than an explicit "we used to". A comment that justifies the
code against an alternative — "run it on a worker rather than blocking the UI",
"switch panels in `Then` rather than a moment earlier" — is history in disguise
whenever that alternative is what the code did before the change. It reads as
ordinary rationale, but the reader has no way to know the contrast is with a
version that no longer exists.
So the check to apply is: would you have written this comment if you were
writing the file from scratch, with no diff in mind? If not, the sentence
belongs in the commit message.
## Don't justify routine call sites
If the codebase calls a helper in twenty places without explanation, your
twenty-first call site doesn't need one either. A comment there says "something
here is unusual"; when nothing is, it's noise — and it invites exactly the kind
of before/after justification the section above warns about. Look at the
neighboring call sites before writing one: if they're bare, match them.
## Don't present "live with the bug" as an option ## Don't present "live with the bug" as an option
When you're investigating a defect and laying out fix options for the user, When you're investigating a defect and laying out fix options for the user,
@ -283,7 +472,7 @@ So:
- For changes to `userConfig` fields specifically, don't edit - For changes to `userConfig` fields specifically, don't edit
`docs-master/Config.md` by hand either — the relevant section is `docs-master/Config.md` by hand either — the relevant section is
auto-generated from the struct field doc comments. After editing the auto-generated from the struct field doc comments. After editing the
struct, run `make generate` and include the regenerated struct, run `just generate` and include the regenerated
`docs-master/Config.md` (and `schema-master/config.json`) in your commit. `docs-master/Config.md` (and `schema-master/config.json`) in your commit.
- Don't hard-wrap the doc comments on `userConfig` fields. This applies - Don't hard-wrap the doc comments on `userConfig` fields. This applies
*only* to `userConfig`, because those comments are fed through the doc *only* to `userConfig`, because those comments are fed through the doc
@ -303,3 +492,12 @@ Never run `find` (or similar) from `/` or other paths outside the project. All
third-party code we use is vendored under `vendor/`, so dependency sources are third-party code we use is vendored under `vendor/`, so dependency sources are
reachable from inside the working tree — search there instead of the host reachable from inside the working tree — search there instead of the host
filesystem. filesystem.
## gocui is in-tree, not a dependency
The `gocui` TUI library is a fork maintained directly in this repo under
`pkg/gocui` — it's an ordinary package, not a Go module dependency. Don't look
for it in `go.mod`/`go.sum` or the module cache (`$GOMODCACHE`); it isn't
there. When you need to read or change gocui internals (the task manager, the
event loop, worker/UI-thread dispatch, view rendering), edit `pkg/gocui`
directly.

View file

@ -1,273 +1,35 @@
# Contributing # Contributing
This project does not accept pull requests. ## The short version
In todays world of agentic coding I have decided that it no longer makes sense for me to look at incoming pull requests. As far as I can tell, the vast majority of these is AI-generated these days, which in itself is not necessarily a bad thing; however, there's no way for me to tell whether the person posting the PR actually understands anything about the code that is being contributed or not, and I don't feel like spending time and energy on finding out whether they do. This project does not accept pull requests. Don't bother making one, it won't be merged.
Now you might ask why this even matters; coding agents are capable of producing amazingly high-quality code, so why is it important that the person opening the PR understands it, as long as the code works and tests are green? It does actually matter very much to me. AI generated code needs to be carefully reviewed and iterated on, and it is the contributor's job to do that, not mine. And I have no idea to what extent the contributor has done this, or whether they are even capable of it. However, there are other forms of contributions that are very welcome and encouraged; see below for what those are.
Every PR needs work and iterations until it is mergeable, whether manually coded or AI generated (even very good ones do), and if I don't know whether the person posting the PR will act on my review feedback themselves or just pass it on to their coding agent (which I guess is the much more likely case today), then it doesn't make sense for me to work with them. ## Why no PRs?
For this reason I will close incoming pull requests by default from now on, without comment. Sorry if this sounds hostile, but honestly I don't feel I have much of a choice if I want maintaining this project to still be enjoyable for me. There are two main reasons for this, and I want to be very honest about them:
With that said, if you are indeed serious about contributing a high-quality PR to lazygit, and you are familiar with go, and you have learned enough about lazygit's code base to tell whether your changes are good, then do raise an issue and explain what you are planning to do, and somehow make it plausible that your PR will be worth my time reviewing it. In such a case I might make an exception from the default rule. - I am maintaining lazygit for fun, as a hobby in my free time (which is quite limited). I'd like to spend my free time on things that I enjoy doing. I enjoy working on lazygit's code and improving it myself; I don't enjoy reviewing PRs. It's that simple, really. Reviewing PRs takes a lot of time; time that I would rather spend on developing lazygit myself.
- Even if I had the time and inclination to review PRs, this has become quite difficult today: most PRs nowadays are AI-generated to some extent (often completely), which in itself is not necessarily a bad thing; I heavily use AI myself these days, and I get great results from it. However, agentic coding needs to be guided by humans so that the results are good, and for contributed PRs I can't tell to what extent the human contributor did this, or is even capable of it; and I don't want to do the work of guiding a contributor's coding agent. If I post PR review feedback and have to suspect that the contributor simply passes it on to their coding agent, then that is a work mode that doesn't make sense to me, and I would rather just drive my own agent to do the work.
In the future I might also consider adopting a vouch system similar to [Ghostty's](https://github.com/ghostty-org/ghostty/blob/main/CONTRIBUTING.md#first-time-contributors), but right now I feel the effort needed to set this up and maintain is not justified given the rather low number of high-quality contributions I have seen in recent times. ### Why it might still make sense to post a PR
Even though we no longer accept pull requests, I find it important to emphasize that Lazygit is still a community project, and non-PR contributions are still very welcome. Do file issues for bug reports or feature requests, and help shape the future of lazygit by actively participating in discussing UX designs. Also, the localization system very much depends on everybody's help with translating texts (see https://crowdin.com/project/lazygit). I can think of two such reasons:
--- - You implemented a lazygit improvement that you want to use yourself; in this case it could make sense to let others merge this change into their forks if they find it useful too. And if enough people say they want the feature, this can persuade me to add it, so putting it out there to give it visibility can be helpful.
- You posted an issue for a feature request, and have a prototype that implements it; it could be useful to publish the branch as a draft PR to better illustrate how the feature works.
The remainder of this document is the old version from a time when contributing pull requests was still encouraged. Keeping it here in case I reconsider my policy in the future. For this reason I usually don't close pull requests to give them more visibility. Just don't expect your PR to be merged.
## PR walkthrough ## So how can I contribute then?
[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step. There are other forms of contributions to a project besides source code that are very welcome and encouraged; for instance:
## Design principles - File issues for bugs that you find, and I'll do my best to take care of fixing them (if they are important enough).
- File feature requests for new functionality that you want to see in lazygit. I have a lot of ideas for future improvement myself, but I have also implemented a lot of feature ideas that weren't mine, and I'm grateful for those ideas. (Of course, there are also lots of feature requests that I don't implement, so don't be disappointed if I don't jump on yours.)
- Help make other people's bug reports reproducible. Sometimes people report bugs that they have only seen once, and in such a case it can be helpful to come up with reproducible scenarios.
- Help complete or improve the translation into other languages; join https://crowdin.com/project/lazygit for that.
- Run a master build! This is probably the most valuable way to help me. Test the latest master not just by occasionally trying it, but by actually using it for your daily work; report any issues that you find. This will help prevent having to release hotfix updates for regressions that are only noticed by users updating to a new release.
See [here](./VISION.md) for a set of design principles that we want to consider when building a feature or making a change. Importantly, if you file issues (whether bug reports or feature requests), stay around to answer questions and discuss your issue. There are few things that I find more annoying than spending time on responding to someone's issue (sometimes even making a PR that addresses it), and to then never hear from the OP again. So please set up your Github notifications so that you see when there's activity on your issue, and continue to participate.
## Codebase guide
[This doc](./docs/dev/Codebase_Guide.md) explains:
- what the different packages in the codebase are for
- where important files live
- important concepts in the code
- how the event loop works
- other useful information
## All code changes happen through Pull Requests
Pull requests are the best way to propose changes to the codebase. We actively
welcome your pull requests:
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've added code that needs documentation, update the documentation.
4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
5. Issue that pull request!
Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch.
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
## Commit history
We value a clean and useful commit history, so please take some time to organize your commits so that they make sense. Don't assume that they will be squashed on merge anyway; we don't do that here.
In particular:
- Refactorings and behavior changes should be in separate commits. There are very few exceptions where this is not possible, but in my experience they are very rare.
- Strive for minimal commits; every change that is independent from other changes should be in a commit of its own (with a good commit message that explains why the change is made).
- When you need to iterate over your implementation during review (e.g. because you discovered a bug, or a maintainer requested changes), don't just pile new commits on top. Use fixup commits to make your changes transparent while still maintaining a good commit history. If you don't know what that means, [here's a brief introduction](docs/Fixup_Commits.md).
## A note about AI
It has become common recently to throw an issue at a coding agent and submit whatever comes out of it as a PR. This is not appreciated here, and I will close PRs where I can tell this was the case, or where I even suspect it was the case.
Some of these PRs may actually be good and useful, but many are not, and it's not a good use of my time as a maintainer to look at generated PRs to decide. This is the job of the PR's contributor, and if you don't speak enough go or can't be bothered to get familiar enough with lazygit's codebase to tell, then don't contribute the PR.
## Running in a VSCode dev container
If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so:
![image](https://user-images.githubusercontent.com/8456633/201500508-0d55f99f-5035-4a6f-a0f8-eaea5c003e5d.png)
This requires that:
- you have docker installed
- you have the dev containers extension installed in VSCode
See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers.
## Running in a Github Codespace
If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace. First fork the repo, then click to create a codespace:
![image](https://user-images.githubusercontent.com/8456633/201500566-ffe9105d-6030-4cc7-a525-6570b0b413a2.png)
To run lazygit from within the integrated terminal just go `go run main.go`
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
## Using Nix for development
If you use Nix, you can leverage the included flake to set up a complete development environment with all necessary dependencies:
```sh
nix develop
```
This will drop you into a development shell that includes:
- Latest Go toolchain
- golangci-lint for code linting
- git and make
You can also build and run lazygit using nix:
```sh
# Build lazygit
nix build
# Run lazygit directly
nix run
```
The nix flake supports multiple architectures (x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin) and provides a consistent development environment across different systems.
## Code of conduct
Please note by participating in this project, you agree to abide by the [code of conduct].
[code of conduct]: https://github.com/jesseduffield/lazygit/blob/master/CODE-OF-CONDUCT.md
## Any contributions you make will be under the MIT Software License
In short, when you submit code changes, your submissions are understood to be
under the same [MIT License](http://choosealicense.com/licenses/mit/) that
covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/jesseduffield/lazygit/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new
issue](https://github.com/jesseduffield/lazygit/issues/new); it's that easy!
## Go
This project is written in Go. Go is an opinionated language with strict idioms, but some of those idioms are a little extreme. Some things we do differently:
1. There is no shame in using `self` as a receiver name in a struct method. In fact we encourage it
2. There is no shame in prefixing an interface with 'I' instead of suffixing with 'er' when there are several methods on the interface.
3. If a struct implements an interface, we make it explicit with something like:
```go
var _ MyInterface = &MyStruct{}
```
This makes the intent clearer and means that if we fail to satisfy the interface we'll get an error in the file that needs fixing.
### Code Formatting
To check code formatting [gofumpt](https://pkg.go.dev/mvdan.cc/gofumpt#section-readme) (which is a bit stricter than [gofmt](https://pkg.go.dev/cmd/gofmt)) is used.
VSCode will format the code correctly if you tell the Go extension to use `gofumpt` via your [`settings.json`](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson)
by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#gofumpt-bool) to `true`:
```jsonc
// .vscode/settings.json
{
"gopls": {
"formatting.gofumpt": true
}
}
```
To run gofumpt from your terminal go:
```
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
```
## Programming Font
Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these.
## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package.
### For developers adding new text
If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc).
Note, we use 'Sentence case' for everything (so no 'Title Case' or 'whatever-it's-called-when-there's-no-capital-letters-case')
### For translators
Lazygit translations are managed through [Crowdin](https://crowdin.com/project/lazygit/). If you'd like to contribute translations:
1. Join the Crowdin project at https://crowdin.com/project/lazygit/
2. Select your target language and help translate missing strings
3. The translation files in `pkg/i18n/translations/` are managed by the maintainers - please don't edit them directly
For detailed information about the translation process, including how maintainers sync translations, see `pkg/i18n/translations/README.md`.
## Debugging
The easiest way to debug lazygit is to have two terminal tabs open at once: one for running lazygit (via `go run main.go -debug` in the project root) and one for viewing lazygit's logs (which can be done via `go run main.go --logs` or just `lazygit --logs`).
From most places in the codebase you have access to a logger e.g. `gui.Log.Warn("blah")` or `self.c.Log.Warn("blah")`.
If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself.
If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by setting the `LAZYGIT_LOG_PATH` environment variable and using `logs.Global.Warn("blah")`. This is a global logger that's only intended for development purposes.
If you keep having to do some setup steps to reproduce an issue, read the Testing section below to see how to create an integration test by recording a lazygit session. It's pretty easy!
### VSCode debugger
If you want to trigger a debug session from VSCode, you can use the following snippet. Note that the `console` key is, at the time of writing, still an experimental feature.
```jsonc
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "debug lazygit",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "main.go",
"args": ["--debug"],
"console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging
}
]
}
```
## Profiling
If you want to investigate what's contributing to CPU or memory usage, see [this separate document](docs/dev/Profiling.md).
## Testing
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
## Updating Gocui
Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow:
1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`)
3. Raise a PR on the gocui repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root:
```sh
./scripts/bump_gocui.sh
```
5. Raise a PR in lazygit with those changes
## Updating Lazycore
[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do:
1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit
2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch)
3. Raise a PR on the lazycore repo with your changes
4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root:
```sh
./scripts/bump_lazycore.sh
```
Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task'
5. Raise a PR in lazygit with those changes
## Improvements
If you can think of any way to improve these docs let us know.

View file

@ -36,10 +36,11 @@ generate:
.PHONY: format .PHONY: format
format: format:
gofumpt -l -w . go tool gofumpt -l -w .
.PHONY: lint .PHONY: lint
lint: lint:
./scripts/gofumpt-check.sh
./scripts/golangci-lint-shim.sh run ./scripts/golangci-lint-shim.sh run
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md. # For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
@ -69,4 +70,4 @@ record-demo:
.PHONY: vendor .PHONY: vendor
vendor: vendor:
go mod vendor && go mod tidy go mod tidy && go mod vendor

View file

@ -118,7 +118,7 @@ If you're a mere mortal like me and you're tired of hearing how powerful git is
- [Changing Directory On Exit](#changing-directory-on-exit) - [Changing Directory On Exit](#changing-directory-on-exit)
- [Undo/Redo](#undoredo) - [Undo/Redo](#undoredo)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Custom Pagers](#custom-pagers) - [Custom Diff Renderers](#custom-diff-renderers)
- [Custom Commands](#custom-commands) - [Custom Commands](#custom-commands)
- [Git flow support](#git-flow-support) - [Git flow support](#git-flow-support)
- [Contributing](#contributing) - [Contributing](#contributing)
@ -423,6 +423,7 @@ nix-shell -p lazygit
# or with flakes enabled # or with flakes enabled
nix run nixpkgs#lazygit nix run nixpkgs#lazygit
``` ```
Or you can add lazygit to your `configuration.nix` using the `environment.systemPackages` option. Or you can add lazygit to your `configuration.nix` using the `environment.systemPackages` option.
More details can be found via NixOS search [page](https://search.nixos.org/). More details can be found via NixOS search [page](https://search.nixos.org/).
@ -431,6 +432,7 @@ More details can be found via NixOS search [page](https://search.nixos.org/).
This repository includes a nix flake that provides the latest development version and additional development tools: This repository includes a nix flake that provides the latest development version and additional development tools:
**Run lazygit directly from the repository:** **Run lazygit directly from the repository:**
```sh ```sh
nix run github:jesseduffield/lazygit nix run github:jesseduffield/lazygit
# or from a local clone # or from a local clone
@ -438,6 +440,7 @@ nix run .
``` ```
**Build lazygit from source:** **Build lazygit from source:**
```sh ```sh
nix build github:jesseduffield/lazygit nix build github:jesseduffield/lazygit
# or from a local clone # or from a local clone
@ -446,6 +449,7 @@ nix build .
**Development environment:** **Development environment:**
For contributors, the flake provides a development shell with Go toolchain, development tools, and dependencies: For contributors, the flake provides a development shell with Go toolchain, development tools, and dependencies:
```sh ```sh
nix develop github:jesseduffield/lazygit nix develop github:jesseduffield/lazygit
# or from a local clone # or from a local clone
@ -453,12 +457,14 @@ nix develop
``` ```
The development shell includes: The development shell includes:
- Go toolchain - Go toolchain
- git and make - git and make
- Proper environment variables for development - Proper environment variables for development
**Using in other flakes:** **Using in other flakes:**
The flake also provides an overlay for easy integration into other flake-based projects: The flake also provides an overlay for easy integration into other flake-based projects:
```nix ```nix
{ {
inputs.lazygit.url = "github:jesseduffield/lazygit"; inputs.lazygit.url = "github:jesseduffield/lazygit";
@ -584,9 +590,9 @@ See the [docs](/docs/Undoing.md)
Check out the [configuration docs](docs/Config.md). Check out the [configuration docs](docs/Config.md).
### Custom Pagers ### Custom Diff Renderers
See the [docs](docs/Custom_Pagers.md) See the [docs](docs/Custom_DiffRenderers.md)
### Custom Commands ### Custom Commands

View file

@ -66,8 +66,8 @@ gui:
# The number of spaces per tab; used for everything that's shown in the main # The number of spaces per tab; used for everything that's shown in the main
# view, but probably mostly relevant for diffs. # view, but probably mostly relevant for diffs.
# Note that when using a pager, the pager has its own tab width setting, so you # Note that when using a diff renderer, the renderer has its own tab width
# need to pass it separately in the pager command. # setting, so you need to pass it separately in the renderer command.
tabWidth: 4 tabWidth: 4
# If true, capture mouse events. # If true, capture mouse events.
@ -110,6 +110,26 @@ gui:
# is true. # is true.
expandedSidePanelWeight: 2 expandedSidePanelWeight: 2
# If true, don't give a side panel more height than it needs to show its
# content; when all panels fit, the leftover height is shared among them so that
# they still fill the screen.
shrinkSidePanelsToContent: false
# The side panels, in the order they appear from top to bottom.
# Each entry is a list of one or more names that share a single panel as tabs
# (cycle through them with the next-tab/previous-tab keys).
# Omit a name to hide it; give a name its own one-element list to promote a tab
# to a top-level panel.
# Valid names are: 'status', 'files', 'worktrees', 'submodules', 'branches',
# 'remotes', 'tags', 'commits', 'reflog', 'stash'. 'files', 'branches', and
# 'commits' must always be included; they can't be hidden.
sidePanels:
- [status]
- [files, worktrees, submodules]
- [branches, remotes, tags]
- [commits, reflog]
- [stash]
# Sometimes the main window is split in two (e.g. when the selected file has # Sometimes the main window is split in two (e.g. when the selected file has
# both staged and unstaged changes). This setting controls how the two sections # both staged and unstaged changes). This setting controls how the two sections
# are split. # are split.
@ -316,13 +336,13 @@ gui:
spinner: spinner:
# The frames of the spinner animation. # The frames of the spinner animation.
frames: frames:
- '|' - ●∙∙
- / - ∙●∙
- '-' - ∙∙●
- \ - ∙●∙
# The "speed" of the spinner in milliseconds. # The "speed" of the spinner in milliseconds.
rate: 50 rate: 180
# Status panel view. # Status panel view.
# One of 'dashboard' (default) | 'allBranchesLog' # One of 'dashboard' (default) | 'allBranchesLog'
@ -340,30 +360,39 @@ gui:
# Config relating to git # Config relating to git
git: git:
# Array of pagers. Each entry has the following format: # Array of diff renderers. Each entry has the following format:
# #
# # Value of the --color arg in the git diff command. Some pagers want # # The type of diff renderer. One of: 'stdinFilter' (default) | 'extDiff'
# # this to be set to 'always' and some want it set to 'never' # # | 'rawGit'
# type: "stdinFilter"
#
# # A name for the diff renderer, shown in the notification when cycling
# # renderers. If not set, the name is derived from the first word of the
# # renderer command.
# name: ""
#
# # Value of the --color arg in the git diff command. Only used for type
# # 'stdinFilter'. Some renderers want this to be set to 'always' and some
# # want it set to 'never'.
# colorArg: "always" # colorArg: "always"
# #
# # The command to use for rendering diffs. This is either a stdinFilter or
# # an external diff command, depending on the type field; not applicable if
# # the type is 'rawGit'.
# # e.g. # # e.g.
# # diff-so-fancy # # diff-so-fancy
# # delta --dark --paging=never # # delta --dark --paging=never
# # ydiff -p cat -s --wrap --width={{columnWidth}} # # ydiff -p cat
# pager: "" # # difft --color=always
# command: ""
# #
# # e.g. 'difft --color=always' # # Extra arguments (array of strings) passed to the git command. Only
# externalDiffCommand: "" # # applicable if the type is 'rawGit'.
# args: []
# #
# # If true, Lazygit will use git's `diff.external` config for paging. # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_DiffRenderers.md
# # The advantage over `externalDiffCommand` is that this can be
# # configured per file type in .gitattributes; see
# # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
# useExternalDiffGitConfig: false
#
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
# for more information. # for more information.
pagers: [] diffRenderers: []
# Config relating to committing # Config relating to committing
commit: commit:
@ -406,6 +435,11 @@ git:
# If true, periodically refresh files and submodules # If true, periodically refresh files and submodules
autoRefresh: true autoRefresh: true
# If true, poll the repo periodically for external ref changes (commits, branch
# updates, checkouts made outside lazygit) and refresh when one is detected.
# Independent of autoRefresh, which only governs the files panel.
autoDetectExternalChanges: true
# If not "none", lazygit will automatically fast-forward local branches to match # If not "none", lazygit will automatically fast-forward local branches to match
# their upstream after fetching. Applies to branches that are not the currently # their upstream after fetching. Applies to branches that are not the currently
# checked out branch, and only to those that are strictly behind their upstream # checked out branch, and only to those that are strictly behind their upstream
@ -499,6 +533,15 @@ git:
# to 40 to disable truncation. # to 40 to disable truncation.
truncateCopiedCommitHashesTo: 12 truncateCopiedCommitHashesTo: 12
# Config relating to git worktrees
worktree:
# Default parent directory for new worktrees. It is offered as a candidate
# location alongside the parent directories of any worktrees you already have.
# A relative path is resolved against the repository's root directory, so
# "../worktrees" sits beside the repo and ".worktrees" sits inside it.
# A leading "~" is expanded to your home directory, so "~/worktrees" works.
defaultPath: ""
# Periodic update checks # Periodic update checks
update: update:
# One of: 'prompt' (default) | 'background' | 'never' # One of: 'prompt' (default) | 'background' | 'never'
@ -517,6 +560,11 @@ refresher:
# Auto-fetch can be disabled via option 'git.autoFetch'. # Auto-fetch can be disabled via option 'git.autoFetch'.
fetchInterval: 60 fetchInterval: 60
# Interval in seconds at which lazygit polls for external ref changes (commits,
# branch updates, checkouts made outside lazygit).
# Detection can be disabled via option 'git.autoDetectExternalChanges'.
externalChangeCheckInterval: 2
# If true, show a confirmation popup before quitting Lazygit # If true, show a confirmation popup before quitting Lazygit
confirmOnQuit: false confirmOnQuit: false
@ -648,6 +696,7 @@ keybinding:
confirmInEditor: [<ctrl+enter>, <ctrl+s>] confirmInEditor: [<ctrl+enter>, <ctrl+s>]
remove: d remove: d
new: "n" new: "n"
newWorktree: w
edit: e edit: e
openFile: o openFile: o
scrollUpMain: [<pgup>, K, <ctrl+u>] scrollUpMain: [<pgup>, K, <ctrl+u>]
@ -666,7 +715,8 @@ keybinding:
prevTab: '[' prevTab: '['
nextScreenMode: + nextScreenMode: +
prevScreenMode: _ prevScreenMode: _
cyclePagers: '|' cycleDiffRenderers: '|'
cycleDiffRenderersReverse: \
undo: z undo: z
redo: Z redo: Z
filteringMenu: <ctrl+s> filteringMenu: <ctrl+s>
@ -681,6 +731,7 @@ keybinding:
increaseRenameSimilarityThreshold: ) increaseRenameSimilarityThreshold: )
decreaseRenameSimilarityThreshold: ( decreaseRenameSimilarityThreshold: (
openDiffTool: <ctrl+t> openDiffTool: <ctrl+t>
editConfig: <alt+shift+c>
status: status:
checkForUpdate: u checkForUpdate: u
recentRepos: <enter> recentRepos: <enter>
@ -726,8 +777,6 @@ keybinding:
fetchRemote: f fetchRemote: f
addForkRemote: F addForkRemote: F
sortOrder: s sortOrder: s
worktrees:
viewWorktreeOptions: w
commits: commits:
squashDown: s squashDown: s
renameCommit: r renameCommit: r
@ -1059,6 +1108,12 @@ keybinding:
edit: <disabled> # disable 'edit file' edit: <disabled> # disable 'edit file'
``` ```
### Overriding the platform for default keybindings
A few keybindings have different defaults on macOS than on Linux and Windows (e.g. word-wise cursor movement in text inputs uses `alt` on macOS but `ctrl` elsewhere). Lazygit picks these based on the OS it's running on, but you can override that with the `LAZYGIT_KEYBINDING_PLATFORM` environment variable. Set it to `darwin`, `linux`, or `windows`; any other value is ignored and the actual OS is used.
This is useful when running lazygit in a Linux container that you access over ssh from a Mac, where you'd rather use the macOS keybindings.
### Example Keybindings For Colemak Users ### Example Keybindings For Colemak Users
```yaml ```yaml

View file

@ -0,0 +1,84 @@
# Custom Diff Renderers
Custom diff renderers are useful for showing a better rendering of a diff than git's builtin raw diff, and using one is strongly recommended (I personally prefer delta myself, but that's a matter of personal preference). There are three types of diff renderers that lazygit supports:
- **stdin filters**, e.g. [delta](#delta) and [diff-so-fancy](#diff-so-fancy). They take git's raw output as stdin and produce something nicer on stdout, and they are hooked up using git's GIT_PAGER mechanism. (These used to be called "custom pagers" in earlier lazygit versions.)
- **external diff programs**, e.g. difftastic; these are called using git's `--ext-diff` flag, and they take over diff generation from git completely rather than post-processing git's output.
- **git's raw output using custom arguments**; mainly useful for `--color-words` (or `--word-diff` if you are color blind).
Diff renderers are configured with the `diffRenderers` array in the `git` section of lazygit's config file; it is an array because you can have multiple entries that you can cycle through with the `|` key. This can be useful if you usually prefer a particular diff renderer, but want to use a different one for certain kinds of diffs.
Fields that are shared by all renderer types:
- **type** The type of diff renderer; choices are `stdinFilter`, `extDiff`, or `rawGit`. `stdinFilter` is the default, because it's the most common one; so you can omit this if you use delta.
- **name** A name that is shown in the status bar toast when cycling renderers; defaults to the first word of the renderer command, but can be useful e.g. to distinguish "delta" from "delta side-by-side" if you have entries for both.
Fields only for `stdinFilter`:
- **command** The command line to use for `GIT_PAGER`.
- **colorArg** whether you want the `--color=always` arg in your `git diff` command. Some diff renderers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most renderers need.
Fields only for `extDiff`:
- **command** The command line to use for the `diff.external` git config. If left empty, it uses the global value of git's `diff.external` config; this can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
You can include the `{{diffContext}}` template variable to pass lazygit's current diff context size (the value controlled by the `{`/`}` keybindings) to the diff tool.
Fields only for `rawGit`:
- **args** The additional arguments to use in the `git diff` or `git show` call (e.g. `--color-words`), as an array of strings.
Here's an example for a multi-renderer setup:
```yaml
git:
diffRenderers:
- command: delta --dark --paging=never
- command: ydiff -p cat
colorArg: never
- type: extDiff
command: difft --color=always --context={{diffContext}}
- type: rawGit
args: [--color-words]
name: color-words
- type: rawGit # git's default diff
name: default
```
## Delta:
```yaml
git:
diffRenderers:
- command: delta --dark --paging=never
```
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `command:` field to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
Note that delta's `--navigate` option doesn't work in lazygit, for technical reasons.
## Diff-so-fancy
```yaml
git:
diffRenderers:
- command: diff-so-fancy
```
![](https://i.imgur.com/rjH1TpT.png)
## ydiff
```yaml
gui:
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
git:
diffRenderers:
- colorArg: never
command: ydiff -p cat
```
![](https://i.imgur.com/vaa8z0H.png)

View file

@ -1,119 +0,0 @@
# Custom Pagers
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel).
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support. However, see [below](#emulating-custom-pagers-on-windows) for a workaround.
Multiple pagers are supported; you can cycle through them with the `|` key. This can be useful if you usually prefer a particular pager, but want to use a different one for certain kinds of diffs.
Pagers are configured with the `pagers` array in the git section; here's an example for a multi-pager setup (use an empty object `{}` for the default builtin diff display that doesn't use a pager):
```yaml
git:
pagers:
- pager: delta --dark --paging=never
- pager: ydiff -p cat -s --wrap --width={{columnWidth}}
colorArg: never
- externalDiffCommand: difft --color=always
- {} # default, no pager used
```
The `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most pagers need.
## Delta:
```yaml
git:
pagers:
- pager: delta --dark --paging=never
```
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
Note that delta's `--navigate` option doesn't work in lazygit, for technical reasons.
## Diff-so-fancy
```yaml
git:
pagers:
- pager: diff-so-fancy
```
![](https://i.imgur.com/rjH1TpT.png)
## ydiff
```yaml
gui:
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
git:
pagers:
- colorArg: never
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
```
![](https://i.imgur.com/vaa8z0H.png)
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
## Using external diff commands
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be
```yaml
git:
pagers:
- externalDiffCommand: difft --color=always
```
The `colorArg` and `pager` options are not used in this case.
You can add whatever extra arguments you prefer for your difftool; for instance
```yaml
git:
pagers:
- externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
```
Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using
```yaml
git:
pagers:
- useExternalDiffGitConfig: true
```
This can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
## Emulating custom pagers on Windows
There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as `lazygit-pager.ps1` at a convenient place on your disk:
```pwsh
#!/usr/bin/env pwsh
$old = $args[1].Replace('\', '/')
$new = $args[4].Replace('\', '/')
$path = $args[0]
git diff --no-index --no-ext-diff $old $new
| %{ $_.Replace($old, $path).Replace($new, $path) }
| delta --width=$env:LAZYGIT_COLUMNS
```
Use the pager of your choice with the arguments you like in the last line of the script. Personally I wouldn't want to use lazygit anymore without delta's `--hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"` args, see [above](#delta).
In your lazygit config, use
```yml
git:
pagers:
- externalDiffCommand: "C:/wherever/lazygit-pager.ps1"
```
The main limitation of this approach compared to a "real" pager is that renames are not displayed correctly; they are shown as if they were modifications of the old file. (This affects only the hunk headers; the diff itself is always correct.)

View file

@ -2,7 +2,7 @@
* [Configuration](./Config.md). * [Configuration](./Config.md).
* [Custom Commands](./Custom_Command_Keybindings.md) * [Custom Commands](./Custom_Command_Keybindings.md)
* [Custom Pagers](./Custom_Pagers.md) * [Custom Diff Renderers](./Custom_DiffRenderers.md)
* [Dev docs](./dev) * [Dev docs](./dev)
* [Keybindings](./keybindings) * [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md) * [Undo/Redo](./Undoing.md)

View file

@ -4,10 +4,14 @@
Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`. Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`.
We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit. In the commits view we don't filter, but search; this is deliberate because you typically care about the commits that come before/after a matching commit.
If you would like both filtering and searching to be enabled on a given view, please raise an issue for this. If you would like both filtering and searching to be enabled on a given view, please raise an issue for this.
## Menu filtering
The keybindings (`?`) and recent repositories menus can be filtered simply by typing. The filter field appears at the bottom of the menu while you type; there is no need to press `/` or confirm the filter before navigating the results.
## Filtering files by status ## Filtering files by status
You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view. You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view.

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Next screen mode (normal/half/fullscreen) | | | `` + `` | Next screen mode (normal/half/fullscreen) | |
| `` _ `` | Prev screen mode | | | `` _ `` | Prev screen mode | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Cancel | | | `` <esc> `` | Cancel | |
| `` ? `` | Open keybindings menu | | | `` ? `` | Open keybindings menu | |
| `` <ctrl+s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Quit | | | `` q, <ctrl+c> `` | Quit | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Edit config file | Open file in external editor. |
| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
@ -110,13 +112,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Confirmation panel ## Confirmation panel
@ -176,6 +178,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Checkout | Checkout selected item. | | `` <space> `` | Checkout | Checkout selected item. |
| `` n `` | New branch | | | `` n `` | New branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | Create pull request | | | `` o `` | Create pull request | |
| `` O `` | View create pull request options | | | `` O `` | View create pull request options | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -195,7 +198,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Main panel (merging) ## Main panel (merging)
@ -203,7 +205,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Pick hunk | | | `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Previous hunk | | | `` <up>, k `` | Previous hunk | |
| `` <down>, j `` | Next hunk | | | `` <down>, j `` | Next hunk | |
| `` <left>, h `` | Previous conflict | | | `` <left>, h `` | Previous conflict | |
@ -280,6 +282,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -287,7 +290,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
@ -297,6 +299,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy branch name to clipboard | | | `` <ctrl+o> `` | Copy branch name to clipboard | |
| `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | New branch | | | `` n `` | New branch | |
| `` w `` | New worktree | |
| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Delete | Delete the remote branch from the remote. |
@ -306,7 +309,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remotes ## Remotes
@ -337,17 +339,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Drop | Remove the stash entry from the stash list. | | `` d `` | Drop | Remove the stash entry from the stash list. |
| `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Rename stash | | | `` r `` | Rename stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Open config file | Open file in default application. |
| `` e `` | Edit config file | Open file in external editor. | | `` e `` | Edit config file | Open file in external editor. |
| `` u `` | Check for update | | | `` u `` | Check for update | |
| `` <enter> `` | Switch to a recent repo | | | `` <enter> `` | Switch to a recent repo | |
@ -365,6 +366,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -372,7 +374,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Submodules ## Submodules
@ -396,13 +397,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. |
| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | Delete | View delete options for local/remote tag. | | `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Worktrees ## Worktrees

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 更新 | Gitの状態を更新します`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 | | `` R `` | 更新 | Gitの状態を更新します`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
| `` + `` | 次の画面モード(通常/半分/全画面) | | | `` + `` | 次の画面モード(通常/半分/全画面) | |
| `` _ `` | 前の画面モード | | | `` _ `` | 前の画面モード | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | キャンセル | | | `` <esc> `` | キャンセル | |
| `` ? `` | キーバインディングメニューを開く | | | `` ? `` | キーバインディングメニューを開く | |
| `` <ctrl+s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 | | `` <ctrl+s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 終了 | | | `` q, <ctrl+c> `` | 終了 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | | `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | | `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
@ -90,13 +92,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | | | `` / `` | 現在のビューをテキストで検索 | |
## コミットファイル ## コミットファイル
@ -136,6 +138,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | | `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
@ -143,7 +146,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | | | `` / `` | 現在のビューをテキストで検索 | |
## サブモジュール ## サブモジュール
@ -168,17 +170,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 | | `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 |
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 | | `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 | | `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
| `` w `` | 新しいワークツリー | |
| `` r `` | スタッシュの名前を変更 | | | `` r `` | スタッシュの名前を変更 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ステータス ## ステータス
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 | | `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` u `` | 更新を確認 | | | `` u `` | 更新を確認 | |
| `` <enter> `` | 最近のリポジトリをチェックアウト | | | `` <enter> `` | 最近のリポジトリをチェックアウト | |
@ -201,13 +202,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | タグをクリップボードにコピー | | | `` <ctrl+o> `` | タグをクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 |
| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 | | `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 |
| `` w `` | 新しいワークツリー | |
| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 | | `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 |
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 | | `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ファイル ## ファイル
@ -286,7 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | ハンクを選択 | | | `` <space> `` | ハンクを選択 | |
| `` b `` | すべてのハンクを選択 | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 前のハンク | | | `` <up>, k `` | 前のハンク | |
| `` <down>, j `` | 次のハンク | | | `` <down>, j `` | 次のハンク | |
| `` <left>, h `` | 前のコンフリクト | | | `` <left>, h `` | 前のコンフリクト | |
@ -325,6 +326,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | | `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
@ -332,7 +334,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## リモート ## リモート
@ -354,6 +355,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | ブランチ名をクリップボードにコピー | | | `` <ctrl+o> `` | ブランチ名をクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 |
| `` n `` | 新しいブランチ | | | `` n `` | 新しいブランチ | |
| `` w `` | 新しいワークツリー | |
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) | | `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 | | `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
| `` d `` | 削除 | リモートからリモートブランチを削除します。 | | `` d `` | 削除 | リモートからリモートブランチを削除します。 |
@ -363,7 +365,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ローカルブランチ ## ローカルブランチ
@ -375,6 +376,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
| `` n `` | 新しいブランチ | | | `` n `` | 新しいブランチ | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` o `` | プルリクエストを作成 | | | `` o `` | プルリクエストを作成 | |
| `` O `` | プルリクエスト作成オプションを表示 | | | `` O `` | プルリクエスト作成オプションを表示 | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -394,7 +396,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ワークツリー ## ワークツリー

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | | | `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | |
| `` _ `` | 이전 스크린 모드 | | | `` _ `` | 이전 스크린 모드 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | 취소 | | | `` <esc> `` | 취소 | |
| `` ? `` | 매뉴 열기 | | | `` ? `` | 매뉴 열기 | |
| `` <ctrl+s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 종료 | | | `` q, <ctrl+c> `` | 종료 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | 설정 파일 수정 | Open file in external editor. |
| `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
@ -67,6 +69,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | | | `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
@ -74,7 +77,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Secondary ## Secondary
@ -93,10 +95,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Drop | Remove the stash entry from the stash list. | | `` d `` | Drop | Remove the stash entry from the stash list. |
| `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Rename stash | | | `` r `` | Rename stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Sub-commits ## Sub-commits
@ -109,6 +111,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | | | `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
@ -116,7 +119,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## Worktrees ## Worktrees
@ -142,7 +144,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Pick hunk | | | `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 이전 hunk를 선택 | | | `` <up>, k `` | 이전 hunk를 선택 | |
| `` <down>, j `` | 다음 hunk를 선택 | | | `` <down>, j `` | 다음 hunk를 선택 | |
| `` <left>, h `` | 이전 충돌을 선택 | | | `` <left>, h `` | 이전 충돌을 선택 | |
@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | 체크아웃 | Checkout selected item. | | `` <space> `` | 체크아웃 | Checkout selected item. |
| `` n `` | 새 브랜치 생성 | | | `` n `` | 새 브랜치 생성 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | 풀 리퀘스트 생성 | | | `` o `` | 풀 리퀘스트 생성 | |
| `` O `` | 풀 리퀘스트 생성 옵션 | | | `` O `` | 풀 리퀘스트 생성 옵션 | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -229,14 +232,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 상태 ## 상태
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 설정 파일 열기 | Open file in default application. |
| `` e `` | 설정 파일 수정 | Open file in external editor. | | `` e `` | 설정 파일 수정 | Open file in external editor. |
| `` u `` | 업데이트 확인 | | | `` u `` | 업데이트 확인 | |
| `` <enter> `` | 최근에 사용한 저장소로 전환 | | | `` <enter> `` | 최근에 사용한 저장소로 전환 | |
@ -277,6 +278,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 브랜치명을 클립보드에 복사 | | | `` <ctrl+o> `` | 브랜치명을 클립보드에 복사 | |
| `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 새 브랜치 생성 | | | `` n `` | 새 브랜치 생성 | |
| `` w `` | New worktree | |
| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | | `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | 삭제 | Delete the remote branch from the remote. | | `` d `` | 삭제 | Delete the remote branch from the remote. |
@ -286,7 +288,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 커밋 ## 커밋
@ -322,13 +323,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## 커밋 파일 ## 커밋 파일
@ -365,13 +366,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. | | `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. |
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | 삭제 | View delete options for local/remote tag. | | `` d `` | 삭제 | View delete options for local/remote tag. |
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 파일 ## 파일

View file

@ -9,29 +9,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+r> `` | Wissel naar een recente repo | | | `` <ctrl+r> `` | Wissel naar een recente repo | |
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | | | `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | |
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | | | `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | |
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | | `` @ `` | Commandolog opties weergeven | Bekijk commandolog opties, bijv. commandolog tonen/verbergen en focus. |
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | | `` P `` | Push | Push de huidige branch naar de bijbehorende upstream-branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | | `` p `` | Pull | Pull wijzigingen van de remote voor de huidige branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | | `` : `` | Voer shellcommando uit | Bring up a prompt where you can enter a shell command to execute. |
| `` <ctrl+p> `` | Bekijk aangepaste patch opties | | | `` <ctrl+p> `` | Bekijk aangepaste patch opties | |
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. | | `` m `` | Bekijk merge/rebase opties | Toon abort/continue/skip opties voor huidige merge/rebase. |
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Volgende scherm modus (normaal/half/groot) | | | `` + `` | Volgende scherm modus (normaal/half/groot) | |
| `` _ `` | Vorige scherm modus | | | `` _ `` | Vorige scherm modus | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Annuleren | | | `` <esc> `` | Annuleren | |
| `` ? `` | Open menu | | | `` ? `` | Open menu | |
| `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | | `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q, <ctrl+c> `` | Quit | | | `` q, <ctrl+c> `` | Afsluiten | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Pauzeer de applicatie | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Witruimte weergeven in-/uitschakelen | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Verander config bestand | Open bestand in externe editor. |
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | Redo (via reflog) (experimenteel) | Het reflog wordt gebruikt om te bepalen welk git commando moet worden gebruikt om het laatste git commando te herhalen. Wijzigingen aan de working tree worden niet meegenomen, alleen command's zijn kandidaten. |
## Lijstpaneel navigatie ## Lijstpaneel navigatie
@ -45,8 +47,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <shift+down> `` | Range select down | | | `` <shift+down> `` | Range select down | |
| `` <shift+up> `` | Range select up | | | `` <shift+up> `` | Range select up | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
| `` H `` | Scroll left | | | `` H `` | Scroll naar links | |
| `` L `` | Scroll right | | | `` L `` | Scroll naar rechts | |
| `` ] `` | Volgende tabblad | | | `` ] `` | Volgende tabblad | |
| `` [ `` | Vorige tabblad | | | `` [ `` | Vorige tabblad | |
@ -56,15 +58,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | | | `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
| `` <space> `` | Toggle staged | Toggle staged for selected file. | | `` <space> `` | Toggle staged | Toggle staged for selected file. |
| `` <ctrl+b> `` | Filter files by status | | | `` <ctrl+b> `` | Filter bestanden op status | |
| `` y `` | Copy to clipboard | | | `` y `` | Kopieer naar klembord | |
| `` c `` | Commit veranderingen | Commit staged changes. | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
| `` w `` | Commit veranderingen zonder pre-commit hook | | | `` w `` | Commit veranderingen zonder pre-commit hook | |
| `` A `` | Wijzig laatste commit | | | `` A `` | Wijzig laatste commit | |
| `` C `` | Commit veranderingen met de git editor | | | `` C `` | Commit veranderingen met de git editor | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | Edit | Open file in external editor. | | `` e `` | Edit | Open bestand in externe editor. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` i `` | Ignore or exclude file | | | `` i `` | Ignore or exclude file | |
| `` r `` | Refresh bestanden | | | `` r `` | Refresh bestanden | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | | `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
@ -73,13 +75,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <enter> `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | | `` <enter> `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. | | `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. |
| `` g `` | Bekijk upstream reset opties | | | `` g `` | Bekijk upstream reset opties | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | | `` D `` | Resetten | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
@ -89,7 +91,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | Bevestig | | | `` <enter> `` | Bevestig | |
| `` <esc> `` | Sluiten | | | `` <esc> `` | Sluiten | |
| `` <ctrl+o> `` | Copy to clipboard | | | `` <ctrl+o> `` | Kopieer naar klembord | |
## Branches ## Branches
@ -97,18 +99,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer branch name naar klembord | | | `` <ctrl+o> `` | Kopieer branch name naar klembord | |
| `` i `` | Laat git-flow opties zien | | | `` i `` | Laat git-flow opties zien | |
| `` <space> `` | Uitchecken | Checkout selected item. | | `` <space> `` | Uitchecken | Geselecteerd item uitchecken. |
| `` n `` | Nieuwe branch | | | `` n `` | Nieuwe branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` o `` | Maak een pull-request | | | `` o `` | Maak een pull-request | |
| `` O `` | Bekijk opties voor pull-aanvraag | | | `` O `` | Bekijk opties voor pull-aanvraag | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
| `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | | | `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | |
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | | `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | | | `` - `` | Vorige branch uitchecken | |
| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | | `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. | | `` d `` | Verwijderen | View delete options for local/remote branch. |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. | | `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. |
| `` T `` | Creëer tag | | | `` T `` | Creëer tag | |
@ -116,10 +119,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Bekijk reset opties | | | `` g `` | Bekijk reset opties | |
| `` R `` | Hernoem branch | | | `` R `` | Hernoem branch | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | | `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Commit bericht ## Commit bericht
@ -134,18 +136,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | | | `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
| `` y `` | Copy to clipboard | | | `` y `` | Kopieer naar klembord | |
| `` c `` | Uitchecken | Bestand uitchecken | | `` c `` | Uitchecken | Bestand uitchecken |
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand | | `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Edit | Open file in external editor. | | `` e `` | Edit | Open bestand in externe editor. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
@ -159,36 +161,36 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | | `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Hernoem commit | Reword the selected commit's message. | | `` r `` | Hernoem commit | Herschrijf de commit message van de geselecteerde commit. |
| `` R `` | Hernoem commit met editor | | | `` R `` | Hernoem commit met editor | |
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | | `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | Wijzig commit | | `` e `` | Bewerken (start interactieve rebase) | Wijzig commit |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. | | `` i `` | Start interactieve rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | Kies commit (wanneer midden in rebase) | | `` p `` | Pick | Kies commit (wanneer midden in rebase) |
| `` F `` | Creëer fixup commit | Creëer fixup commit | | `` F `` | Creëer fixup commit | Creëer fixup commit |
| `` S `` | Apply fixup commits | Squash bovenstaande commits | | `` S `` | Apply fixup commits | Squash bovenstaande commits |
| `` <ctrl+j>, <alt+down> `` | Verplaats commit 1 naar beneden | | | `` <ctrl+j>, <alt+down> `` | Verplaats commit 1 naar beneden | |
| `` <ctrl+k>, <alt+up> `` | Verplaats commit 1 naar boven | | | `` <ctrl+k>, <alt+up> `` | Verplaats commit 1 naar boven | |
| `` V `` | Plak commits (cherry-pick) | | | `` V `` | Plak commits (cherry-pick) | |
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | | `` B `` | Markeer als basiscommit voor rebase | Selecteer een basiscommit voor de volgende rebase. Als je rebased op een branch worden alleen commits boven de basiscommit meegenomen. Hiervoor wordt het `git rebase --onto` commando gebruikt. |
| `` A `` | Amend | Wijzig commit met staged veranderingen | | `` A `` | Amend | Wijzig commit met staged veranderingen |
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | | `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | | `` t `` | Revert | Maak een revert commit voor de geselecteerde commit, die de wijzigingen in deze commit terugdraait. |
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | | `` T `` | Tag commit | Maak een nieuwe tag die naar de geselecteerde commit wijst. Je wordt gevraagd om een tag naam en optionele omschrijving. |
| `` <ctrl+l> `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | | `` <ctrl+l> `` | Log opties weergeven | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Input prompt ## Input prompt
@ -211,15 +213,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Kies stuk | | | `` <space> `` | Kies stuk | |
| `` b `` | Kies beide stukken | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Selecteer bovenste hunk | | | `` <up>, k `` | Selecteer bovenste hunk | |
| `` <down>, j `` | Selecteer onderste hunk | | | `` <down>, j `` | Selecteer onderste hunk | |
| `` <left>, h `` | Selecteer voorgaand conflict | | | `` <left>, h `` | Selecteer voorgaand conflict | |
| `` <right>, l `` | Selecteer volgende conflict | | | `` <right>, l `` | Selecteer volgende conflict | |
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. | | `` z `` | Ongedaan maken | Undo last merge conflict resolution. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
| `` <esc> `` | Ga terug naar het bestanden paneel | | | `` <esc> `` | Ga terug naar het bestanden paneel | |
## Normaal ## Normaal
@ -239,10 +241,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | Selecteer de vorige hunk | | | `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | | | `` <right>, l `` | Selecteer de volgende hunk | |
| `` v `` | Toggle drag selecteer | | | `` v `` | Toggle drag selecteer | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | Wissel tussen hunk selectie aan of uit | Wissel tussen regel-voor-regel of hunk selectie modus. |
| `` <ctrl+o> `` | Copy selected text to clipboard | | | `` <ctrl+o> `` | Copy selected text to clipboard | |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | | | `` <space> `` | Voeg toe/verwijder lijn(en) in patch | |
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
| `` <esc> `` | Sluit lijn-bij-lijn modus | | | `` <esc> `` | Sluit lijn-bij-lijn modus | |
@ -253,19 +255,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | | | `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
@ -273,27 +275,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer branch name naar klembord | | | `` <ctrl+o> `` | Kopieer branch name naar klembord | |
| `` <space> `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Uitchecken | Geselecteerde remote branch uitchecken als nieuwe locale branch of als detached head. |
| `` n `` | Nieuwe branch | | | `` n `` | Nieuwe branch | |
| `` w `` | New worktree | |
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Verwijderen | Verwijder de remote branch van de remote. |
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch | | `` u `` | Instellen als upstream | Stel in als upstream van uitgecheckte branch |
| `` s `` | Sort order | | | `` s `` | Sort order | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remotes ## Remotes
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | View branches | | | `` <enter> `` | Bekijk branches | |
| `` n `` | Voeg een nieuwe remote toe | | | `` n `` | Voeg een nieuwe remote toe | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | | `` d `` | Verwijderen | Verwijder de geselecteerde remote. Locale branches die een branch tracken van de remote worden niet aangepast. |
| `` e `` | Edit | Wijzig remote | | `` e `` | Edit | Wijzig remote |
| `` f `` | Fetch | Fetch remote | | `` f `` | Fetch | Fetch remote |
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. | | `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. |
@ -314,19 +316,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | Selecteer de vorige hunk | | | `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | | | `` <right>, l `` | Selecteer de volgende hunk | |
| `` v `` | Toggle drag selecteer | | | `` v `` | Toggle drag selecteer | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | Wissel tussen hunk selectie aan of uit | Wissel tussen regel-voor-regel of hunk selectie modus. |
| `` <ctrl+o> `` | Copy selected text to clipboard | | | `` <ctrl+o> `` | Copy selected text to clipboard | |
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged | | `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | | `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` <esc> `` | Ga terug naar het bestanden paneel | | | `` <esc> `` | Ga terug naar het bestanden paneel | |
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | | `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. | | `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | Commit veranderingen | Commit staged changes. | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
| `` w `` | Commit veranderingen zonder pre-commit hook | | | `` w `` | Commit veranderingen zonder pre-commit hook | |
| `` C `` | Commit veranderingen met de git editor | | | `` C `` | Commit veranderingen met de git editor | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Stash ## Stash
@ -337,18 +339,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Laten vallen | Remove the stash entry from the stash list. | | `` d `` | Laten vallen | Remove the stash entry from the stash list. |
| `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` r `` | Rename stash | | | `` w `` | New worktree | |
| `` r `` | Hernoem stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Open config bestand | Open file in default application. | | `` e `` | Verander config bestand | Open bestand in externe editor. |
| `` e `` | Verander config bestand | Open file in external editor. |
| `` u `` | Check voor updates | | | `` u `` | Check voor updates | |
| `` <enter> `` | Wissel naar een recente repo | | | `` <enter> `` | Wissel naar een recente repo | |
| `` a `` | Show/cycle all branch logs | | | `` a `` | Show/cycle all branch logs | |
@ -360,19 +361,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | | | `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Submodules ## Submodules
@ -381,7 +382,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer submodule naam naar klembord | | | `` <ctrl+o> `` | Kopieer submodule naam naar klembord | |
| `` <enter> `` | Enter | Enter submodule | | `` <enter> `` | Enter | Enter submodule |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | | `` d `` | Verwijderen | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | Update selected submodule. | | `` u `` | Update | Update selected submodule. |
| `` n `` | Voeg nieuwe submodule toe | | | `` n `` | Voeg nieuwe submodule toe | |
| `` e `` | Update submodule URL | | | `` e `` | Update submodule URL | |
@ -394,15 +395,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Uitchecken | Geselecteerde tag uitchecken als detached HEAD. |
| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | Creëer tag | Maak een nieuwe tag die naar de huidige commit wijst. Je wordt gevraagd om een tag naam en optionele beschrijving. |
| `` d `` | Delete | View delete options for local/remote tag. | | `` w `` | New worktree | |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` d `` | Verwijderen | View delete options for local/remote tag. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` P `` | Tag pushen | Push de geselecteerde tag naar een remote. Je krijgt de optie een remote te selecteren. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` g `` | Resetten | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Worktrees ## Worktrees
@ -411,6 +412,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` n `` | New worktree | | | `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. | | `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | | | `` o `` | Openen in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | | `` d `` | Verwijderen | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. | | `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | | | `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | |
| `` _ `` | Poprzedni tryb ekranu | | | `` _ `` | Poprzedni tryb ekranu | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Anuluj | | | `` <esc> `` | Anuluj | |
| `` ? `` | Otwórz menu przypisań klawiszy | | | `` ? `` | Otwórz menu przypisań klawiszy | |
| `` <ctrl+s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. | | `` <ctrl+s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Wyjdź | | | `` q, <ctrl+c> `` | Wyjdź | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | | `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | | `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
@ -83,13 +85,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Dodatkowy ## Dodatkowy
@ -120,6 +122,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | | | `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
@ -127,7 +130,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Główny panel (budowanie łatki) ## Główny panel (budowanie łatki)
@ -162,6 +164,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Przełącz | Przełącz wybrany element. | | `` <space> `` | Przełącz | Przełącz wybrany element. |
| `` n `` | Nowa gałąź | | | `` n `` | Nowa gałąź | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` o `` | Utwórz żądanie ściągnięcia | | | `` o `` | Utwórz żądanie ściągnięcia | |
| `` O `` | Zobacz opcje tworzenia pull requesta | | | `` O `` | Zobacz opcje tworzenia pull requesta | |
| `` G `` | Otwórz żądanie ściągnięcia w przeglądarce | | | `` G `` | Otwórz żądanie ściągnięcia w przeglądarce | |
@ -181,7 +184,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Menu ## Menu
@ -207,7 +209,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Wybierz fragment | | | `` <space> `` | Wybierz fragment | |
| `` b `` | Wybierz wszystkie fragmenty | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Poprzedni fragment | | | `` <up>, k `` | Poprzedni fragment | |
| `` <down>, j `` | Następny fragment | | | `` <down>, j `` | Następny fragment | |
| `` <left>, h `` | Poprzedni konflikt | | | `` <left>, h `` | Poprzedni konflikt | |
@ -316,17 +318,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. | | `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. |
| `` d `` | Usuń | Usuń wpis schowka z listy schowka. | | `` d `` | Usuń | Usuń wpis schowka z listy schowka. |
| `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. | | `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. |
| `` w `` | Nowe drzewo pracy | |
| `` r `` | Zmień nazwę schowka | | | `` r `` | Zmień nazwę schowka | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Otwórz plik konfiguracyjny | Otwórz plik w domyślnej aplikacji. |
| `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. | | `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
| `` u `` | Sprawdź aktualizacje | | | `` u `` | Sprawdź aktualizacje | |
| `` <enter> `` | Przełącz na ostatnie repozytorium | | | `` <enter> `` | Przełącz na ostatnie repozytorium | |
@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | | | `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
@ -351,7 +353,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Submoduły ## Submoduły
@ -375,13 +376,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Skopiuj tag do schowka | | | `` <ctrl+o> `` | Skopiuj tag do schowka | |
| `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). | | `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). |
| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | | `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. |
| `` w `` | Nowe drzewo pracy | |
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. | | `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. |
| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. | | `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Zdalne ## Zdalne
@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Kopiuj nazwę gałęzi do schowka | | | `` <ctrl+o> `` | Kopiuj nazwę gałęzi do schowka | |
| `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. | | `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. |
| `` n `` | Nowa gałąź | | | `` n `` | Nowa gałąź | |
| `` w `` | Nowe drzewo pracy | |
| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. | | `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. |
| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. | | `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. |
| `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. | | `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. |
@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. | | `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. |
| `` + `` | Modo de tela seguinte (normal/metade/tela cheia) | | | `` + `` | Modo de tela seguinte (normal/metade/tela cheia) | |
| `` _ `` | Modo de tela anterior | | | `` _ `` | Modo de tela anterior | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Cancelar | | | `` <esc> `` | Cancelar | |
| `` ? `` | Abrir o menu de atalhos do teclado | | | `` ? `` | Abrir o menu de atalhos do teclado | |
| `` <ctrl+s> `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Sair | | | `` q, <ctrl+c> `` | Sair | |
| `` <ctrl+z> `` | Suspender a aplicação | | | `` <ctrl+z> `` | Suspender a aplicação | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | | `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | | `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
@ -92,6 +94,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Verificar | Checar item selecionado | | `` <space> `` | Verificar | Checar item selecionado |
| `` n `` | Nova branch | | | `` n `` | Nova branch | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` o `` | Criar solicitação de pull | | | `` o `` | Criar solicitação de pull | |
| `` O `` | View create pull request options | | | `` O `` | View create pull request options | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Branches remotos ## Branches remotos
@ -121,6 +123,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copiar nome da branch para área de transferência | | | `` <ctrl+o> `` | Copiar nome da branch para área de transferência | |
| `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado | | `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado |
| `` n `` | Nova branch | | | `` n `` | Nova branch | |
| `` w `` | Nova árvore de trabalho | |
| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) | | `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) |
| `` r `` | Refazer | Refazer a branch checada na branch selecionada | | `` r `` | Refazer | Refazer a branch checada na branch selecionada |
| `` d `` | Apagar | Excluir o branch remoto do controle remoto. | | `` d `` | Apagar | Excluir o branch remoto do controle remoto. |
@ -130,7 +133,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Commit arquivos ## Commit arquivos
@ -186,13 +188,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Pesquisar na visualização atual por texto | | | `` / `` | Pesquisar na visualização atual por texto | |
## Etiquetas ## Etiquetas
@ -202,13 +204,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copiar etiqueta para área de transferência | | | `` <ctrl+o> `` | Copiar etiqueta para área de transferência | |
| `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado | | `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado |
| `` n `` | Nova etiqueta | Crie uma nova etiqueta a partir do commit atual. Você será solicitado a digitar um nome e uma descrição opcional. | | `` n `` | Nova etiqueta | Crie uma nova etiqueta a partir do commit atual. Você será solicitado a digitar um nome e uma descrição opcional. |
| `` w `` | Nova árvore de trabalho | |
| `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. | | `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. |
| `` P `` | Empurrar etiqueta | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Empurrar etiqueta | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Input prompt ## Input prompt
@ -271,7 +273,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Escolha o local | | | `` <space> `` | Escolha o local | |
| `` b `` | Pegar todos os pedaços | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Trecho anterior | | | `` <up>, k `` | Trecho anterior | |
| `` <down>, j `` | Próximo trecho | | | `` <down>, j `` | Próximo trecho | |
| `` <left>, h `` | Conflito anterior | | | `` <left>, h `` | Conflito anterior | |
@ -308,6 +310,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -315,7 +318,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Remotes ## Remotes
@ -346,17 +348,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. | | `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. |
| `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. | | `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. |
| `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. | | `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. |
| `` w `` | Nova árvore de trabalho | |
| `` r `` | Renomear o stash | | | `` r `` | Renomear o stash | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Abrir o ficheiro de config | Abrir arquivo no aplicativo padrão. |
| `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. | | `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
| `` u `` | Verificar atualização | | | `` u `` | Verificar atualização | |
| `` <enter> `` | Mudar para um repositório recente | | | `` <enter> `` | Mudar para um repositório recente | |
@ -374,6 +375,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -381,7 +383,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Pesquisar na visualização atual por texto | | | `` / `` | Pesquisar na visualização atual por texto | |
## Submódulos ## Submódulos

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | | | `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | |
| `` _ `` | Предыдущий режим экрана | | | `` _ `` | Предыдущий режим экрана | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Отменить | | | `` <esc> `` | Отменить | |
| `` ? `` | Открыть меню | | | `` ? `` | Открыть меню | |
| `` <ctrl+s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Выйти | | | `` q, <ctrl+c> `` | Выйти | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Редактировать файл конфигурации | Open file in external editor. |
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | | `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | | `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
@ -112,7 +114,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Выбрать эту часть | | | `` <space> `` | Выбрать эту часть | |
| `` b `` | Выбрать все части | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Выбрать предыдущую часть | | | `` <up>, k `` | Выбрать предыдущую часть | |
| `` <down>, j `` | Выбрать следующую часть | | | `` <down>, j `` | Выбрать следующую часть | |
| `` <left>, h `` | Выбрать предыдущий конфликт | | | `` <left>, h `` | Выбрать предыдущий конфликт | |
@ -149,6 +151,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | | | `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
@ -156,7 +159,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Коммиты ## Коммиты
@ -192,13 +194,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Найти | | | `` / `` | Найти | |
## Локальные Ветки ## Локальные Ветки
@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Переключить | Checkout selected item. | | `` <space> `` | Переключить | Checkout selected item. |
| `` n `` | Новая ветка | | | `` n `` | Новая ветка | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | Создать запрос на принятие изменений | | | `` o `` | Создать запрос на принятие изменений | |
| `` O `` | Создать параметры запроса принятие изменений | | | `` O `` | Создать параметры запроса принятие изменений | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -229,7 +232,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Меню ## Меню
@ -258,6 +260,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | | | `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
@ -265,7 +268,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Найти | | | `` / `` | Найти | |
## Подмодули ## Подмодули
@ -313,7 +315,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Открыть файл конфигурации | Open file in default application. |
| `` e `` | Редактировать файл конфигурации | Open file in external editor. | | `` e `` | Редактировать файл конфигурации | Open file in external editor. |
| `` u `` | Проверить обновления | | | `` u `` | Проверить обновления | |
| `` <enter> `` | Переключиться на последний репозиторий | | | `` <enter> `` | Переключиться на последний репозиторий | |
@ -328,13 +329,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. |
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | Delete | View delete options for local/remote tag. | | `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Удалённые ветки ## Удалённые ветки
@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Скопировать название ветки в буфер обмена | | | `` <ctrl+o> `` | Скопировать название ветки в буфер обмена | |
| `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | Новая ветка | | | `` n `` | Новая ветка | |
| `` w `` | New worktree | |
| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | | `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Delete | Delete the remote branch from the remote. |
@ -353,7 +355,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Удалённые репозитории ## Удалённые репозитории
@ -409,8 +410,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. | | `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. |
| `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Переименовать хранилище | | | `` r `` | Переименовать хранилище | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 刷新 | 刷新Git状态即在后台运行`git status`、`git branch`等命令以更新面板内容)。此操作不会执行`git fetch`。 | | `` R `` | 刷新 | 刷新Git状态即在后台运行`git status`、`git branch`等命令以更新面板内容)。此操作不会执行`git fetch`。 |
| `` + `` | 下一屏模式(正常/半屏/全屏) | | | `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | | | `` _ `` | 上一屏模式 | |
| `` \| `` | 切换分页器 | 从已配置的分页器列表中选择下一个分页器 | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | 取消 | | | `` <esc> `` | 取消 | |
| `` ? `` | 打开菜单 | | | `` ? `` | 打开菜单 | |
| `` <ctrl+s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 | | `` <ctrl+s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 退出 | | | `` q, <ctrl+c> `` | 退出 | |
| `` <ctrl+z> `` | 挂起应用程序 | | | `` <ctrl+z> `` | 挂起应用程序 | |
| `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 | | `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 |
| `` <alt+shift+c> `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 | | `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 |
| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 | | `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 |
@ -60,6 +62,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | | | `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
@ -67,7 +70,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 子模块 ## 子模块
@ -104,6 +106,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | | | `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 提交 ## 提交
@ -147,13 +149,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 提交信息 ## 提交信息
@ -225,6 +227,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | 检出 | 检出选中的项目 | | `` <space> `` | 检出 | 检出选中的项目 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` o `` | 创建拉取请求 | | | `` o `` | 创建拉取请求 | |
| `` O `` | 创建拉取请求选项 | | | `` O `` | 创建拉取请求选项 | |
| `` G `` | 在浏览器中打开拉取请求 | | | `` G `` | 在浏览器中打开拉取请求 | |
@ -244,7 +247,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 构建补丁中 ## 构建补丁中
@ -270,13 +272,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 复制标签到剪贴板 | | | `` <ctrl+o> `` | 复制标签到剪贴板 | |
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD | | `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 | | `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 |
| `` w `` | 新建工作树 | |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 | | `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 | | `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 次要 ## 次要
@ -292,7 +294,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 选中区块 | | | `` <space> `` | 选中区块 | |
| `` b `` | 选中所有区块 | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 选择顶部块 | | | `` <up>, k `` | 选择顶部块 | |
| `` <down>, j `` | 选择底部块 | | | `` <down>, j `` | 选择底部块 | |
| `` <left>, h `` | 选择上一个冲突 | | | `` <left>, h `` | 选择上一个冲突 | |
@ -339,7 +341,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 | | `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` u `` | 检查更新 | | | `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | | | `` <enter> `` | 切换到最近的仓库 | |
@ -371,10 +372,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 | | `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 | | `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 | | `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
| `` w `` | 新建工作树 | |
| `` r `` | 重命名贮藏 | | | `` r `` | 重命名贮藏 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 输入提示 ## 输入提示
@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 复制分支名称到剪贴板 | | | `` <ctrl+o> `` | 复制分支名称到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 | | `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` w `` | 新建工作树 | |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) | | `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 | | `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 | | `` d `` | 删除 | 从远程删除远程分支。 |
@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |

View file

@ -9,27 +9,29 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+r> `` | 切換到最近使用的版本庫 | | | `` <ctrl+r> `` | 切換到最近使用的版本庫 | |
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | 向上捲動主面板 | | | `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | 向上捲動主面板 | |
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | 向下捲動主面板 | | | `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | 向下捲動主面板 | |
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. | | `` @ `` | 開啟命令記錄選單 | 檢視命令日誌的選項,例如顯示/隱藏命令日誌以及聚焦命令日誌。 |
| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 | | `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 |
| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 | | `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ) `` | 提高重新命名相似度閾值 | 提高將刪除和新增對視為重新命名所需的相似度閾值。<br><br>預設值可在設定檔中透過鍵 'git.renameSimilarityThreshold' 更改。 |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | 降低重新命名相似度閾值 | 降低將刪除和新增對視為重新命名所需的相似度閾值。<br><br>預設值可在設定檔中透過鍵 'git.renameSimilarityThreshold' 更改。 |
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | 增加差異檢視中變更周圍顯示的上下文量。<br><br>預設值可在設定檔中透過鍵 'git.diffContextSize' 更改。 |
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | 減少差異檢視中變更周圍顯示的上下文量。<br><br>預設值可在設定檔中透過鍵 'git.diffContextSize' 更改。 |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | | `` : `` | 執行 Shell 命令 | 調出可輸入shell命令執行的提示符。 |
| `` <ctrl+p> `` | 檢視自訂補丁選項 | | | `` <ctrl+p> `` | 檢視自訂補丁選項 | |
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. | | `` m `` | 查看合併/變基選項 | 檢視目前合併或變基的中止、繼續、跳過選項。 |
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | 重新整理 | 重新整理Git狀態即在背景執行`git status`、`git branch`等命令以更新面板內容)。此操作不會執行`git fetch`。 |
| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | | | `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | |
| `` _ `` | 上一個螢幕模式 | | | `` _ `` | 上一個螢幕模式 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | 切換差異渲染器 | 選擇已設定的差異渲染器清單中的下一個渲染器。 |
| `` \ `` | 切換差異渲染器(反向) | 選擇已設定的差異渲染器清單中的上一個渲染器。 |
| `` <esc> `` | 取消 | | | `` <esc> `` | 取消 | |
| `` ? `` | 開啟選單 | | | `` ? `` | 開啟選單 | |
| `` <ctrl+s> `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | 檢視篩選路徑選項 | 檢視用於過濾提交日誌的選項,以便僅顯示與過濾器匹配的提交。 |
| `` W, <ctrl+e> `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | | `` W, <ctrl+e> `` | 開啟差異比較選單 | 檢視與比較兩個引用相關的選項,例如與選定的 ref 進行比較,輸入要比較的 ref然後反轉比較方向。 |
| `` q, <ctrl+c> `` | 結束 | | | `` q, <ctrl+c> `` | 結束 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | 掛起應用程式 | |
| `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | 切換是否在差異檢視中顯示空白字元更改。<br><br>預設值可在設定檔中透過鍵 'git.ignoreWhitespaceInDiffView' 更改。 |
| `` <alt+shift+c> `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 | | `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 | | `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
@ -42,21 +44,14 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <, <home> `` | 捲動到頂部 | | | `` <, <home> `` | 捲動到頂部 | |
| `` >, <end> `` | 捲動到底部 | | | `` >, <end> `` | 捲動到底部 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` <shift+down> `` | Range select down | | | `` <shift+down> `` | 向下擴充套件選擇範圍 | |
| `` <shift+up> `` | Range select up | | | `` <shift+up> `` | 向上擴充套件選擇範圍 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
| `` H `` | 向左捲動 | | | `` H `` | 向左捲動 | |
| `` L `` | 向右捲動 | | | `` L `` | 向右捲動 | |
| `` ] `` | 下一個索引標籤 | | | `` ] `` | 下一個索引標籤 | |
| `` [ `` | 上一個索引標籤 | | | `` [ `` | 上一個索引標籤 | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
## 主面板 (補丁生成) ## 主面板 (補丁生成)
| Key | Action | Info | | Key | Action | Info |
@ -64,12 +59,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | 選擇上一段 | | | `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | | | `` <right>, l `` | 選擇下一段 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | 切換程式碼塊選擇 | 切換逐行選擇與程式碼塊選擇模式。 |
| `` <ctrl+o> `` | 複製所選文本至剪貼簿 | | | `` <ctrl+o> `` | 複製所選文本至剪貼簿 | |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | | | `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` d `` | 從提交中移除行 | 從本次提交中移除所選行。此操作會在背景執行互動式變基,因此如果後續提交也修改了這些行,您可能會遇到合併衝突。 |
| `` <esc> `` | 退出自訂補丁建立器 | | | `` <esc> `` | 退出自訂補丁建立器 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
@ -79,8 +74,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <mouse wheel down> (fn+up) `` | 向下捲動 | | | `` <mouse wheel down> (fn+up) `` | 向下捲動 | |
| `` <mouse wheel up> (fn+down) `` | 向上捲動 | | | `` <mouse wheel up> (fn+down) `` | 向上捲動 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` <esc> `` | Exit back to side panel | | | `` <esc> `` | 退出回到側邊面板 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 主面板(合併) ## 主面板(合併)
@ -88,15 +83,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 挑選程式碼片段 | | | `` <space> `` | 挑選程式碼片段 | |
| `` b `` | 挑選所有程式碼片段 | | | `` b `` | 選取兩個區塊 | |
| `` <up>, k `` | 選擇上一段 | | | `` <up>, k `` | 選擇上一段 | |
| `` <down>, j `` | 選擇下一段 | | | `` <down>, j `` | 選擇下一段 | |
| `` <left>, h `` | 選擇上一個衝突 | | | `` <left>, h `` | 選擇上一個衝突 | |
| `` <right>, l `` | 選擇下一個衝突 | | | `` <right>, l `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. | | `` z `` | 復原 | 撤消上次合併衝突解決。 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | 檢視合併衝突選項 | 檢視用於解決合併衝突的選項。 |
| `` <esc> `` | 返回檔案面板 | | | `` <esc> `` | 返回檔案面板 | |
## 主面板(預存) ## 主面板(預存)
@ -106,19 +101,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | 選擇上一段 | | | `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | | | `` <right>, l `` | 選擇下一段 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | 切換程式碼塊選擇 | 切換逐行選擇與程式碼塊選擇模式。 |
| `` <ctrl+o> `` | 複製所選文本至剪貼簿 | | | `` <ctrl+o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) | | `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | | `` d `` | 刪除變更 (git reset) | 選取未暫存的變更時,使用 `git reset` 捨棄變更。選取已暫存的變更時,取消暫存變更。 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <esc> `` | 返回檔案面板 | | | `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. | | `` E `` | 編輯程式碼塊 | 在外部編輯器中編輯選中的程式碼塊。 |
| `` c `` | 提交變更 | 提交暫存區變更 | | `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | | | `` w `` | 沒有預提交 hook 就提交更改 | |
| `` C `` | 使用 git 編輯器提交變更 | | | `` C `` | 使用 git 編輯器提交變更 | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | 尋找 fixup 的基礎提交 | 找出目前變更所依據的提交,以便 amend/fixup。這樣不必逐一檢視分支中的提交來找出要 amend/fixup 的提交。請見文件:<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 功能表 ## 功能表
@ -133,20 +128,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 子模組 ## 子模組
@ -154,12 +149,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製子模組名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製子模組名稱到剪貼簿 | |
| `` <enter> `` | Enter | 進入子模組 | | `` <enter> `` | 進入 | 進入子模組 |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | | `` d `` | 刪除 | 刪除選定的子模組及其相應的目錄。 |
| `` u `` | Update | 更新子模組 | | `` u `` | 更新 | 更新子模組 |
| `` n `` | 新增子模組 | | | `` n `` | 新增子模組 | |
| `` e `` | 更新子模組 URL | | | `` e `` | 更新子模組 URL | |
| `` i `` | Initialize | 初始化子模組 | | `` i `` | 初始化 | 初始化子模組 |
| `` b `` | 查看批量子模組選項 | | | `` b `` | 查看批量子模組選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
@ -167,27 +162,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` n `` | New worktree | | | `` n `` | 新建工作樹 | |
| `` <space> `` | Switch | Switch to the selected worktree. | | `` <space> `` | 切換 | 切換到選中的工作樹。 |
| `` o `` | 在編輯器中開啟 | | | `` o `` | 在編輯器中開啟 | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | | `` d `` | 刪除 | 刪除選定的工作樹。這將刪除工作樹的目錄以及 .git 目錄中有關工作樹的後設資料。 |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 提交 ## 提交
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` b `` | 查看二分選項 | | | `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` s `` | 壓縮 (Squash) | 將已選提交壓縮到該提交之下。這些選定的提交的訊息會附加到該提交的訊息之下。 |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | | `` f `` | 修復 (Fixup) | 將選定的提交合併到其下面的提交中。與壓縮類似,但所選提交的訊息將被丟棄。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` c `` | 設定修復提交資訊 | 設定修復提交的資訊選項。-C 選項表示使用此提交的資訊,而非目標提交的資訊。 |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 | | `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | | | `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | | `` d `` | 刪除提交 | 刪除選中的提交。這將透過變基從分支中刪除該提交,如果該提交修改的內容依賴於後續的提交,則需要解決合併衝突。 |
| `` e `` | 編輯(開始互動變基) | 編輯提交 | | `` e `` | 編輯(開始互動變基) | 編輯提交 |
| `` i `` | 開始互動變基 | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. | | `` i `` | 開始互動變基 | 為分支上的提交啟動互動式變基。這將包括從 HEAD 提交到第一個合併提交或主分支提交的所有提交。<br>如果您想從所選提交啟動互動式變基,請按 `e` |
| `` p `` | 挑選 | 挑選提交 (於變基過程中) | | `` p `` | 挑選 | 挑選提交 (於變基過程中) |
| `` F `` | 建立修復提交 | 為此提交建立修復提交 | | `` F `` | 建立修復提交 | 為此提交建立修復提交 |
| `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 | | `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 |
@ -196,23 +191,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` V `` | 貼上提交 (揀選) | | | `` V `` | 貼上提交 (揀選) | |
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 | | `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
| `` A `` | 修改 | 使用已預存的更改修正提交 | | `` A `` | 修改 | 使用已預存的更改修正提交 |
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. | | `` a `` | 設定/重設提交作者 | 設定或重置提交的作者,或新增其他作者。 |
| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | | `` t `` | 還原 | 為所選提交建立還原提交,這會反向應用所選提交的更改。 |
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | | `` T `` | 打標籤到提交 | 建立一個新標籤指向所選提交。您可以在彈窗中輸入標籤名稱和描述(可選)。 |
| `` <ctrl+l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | | `` <ctrl+l> `` | 開啟記錄選單 | 檢視提交日誌的選項,例如更改排序順序、隱藏 git graph、顯示整個 git graph。 |
| `` G `` | Open pull request in browser | | | `` G `` | 在瀏覽器中開啟拉取請求 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 提交摘要 ## 提交摘要
@ -229,51 +224,51 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | |
| `` y `` | 複製到剪貼簿 | | | `` y `` | 複製到剪貼簿 | |
| `` c `` | 檢出 | 檢出檔案 | | `` c `` | 檢出 | 檢出檔案 |
| `` d `` | 捨棄 | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` d `` | 捨棄 | 放棄對此檔案的提交變更。 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯 | 使用外部編輯器開啟 | | `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` <space> `` | 切換檔案是否包含在補丁中 | 切換檔案是否包含在自定義補丁中。請參閱 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | 切換所有檔案是否包含在補丁中 | 新增或刪除所有提交中的檔案到自定義的補丁中。請參閱 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | 如果已選擇一個檔案則Enter進入該檔案以便您可以向自定義補丁新增/刪除單獨的行。如果選擇了目錄,則切換目錄。 |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | 顯示檔案樹狀視圖 | 在平面佈局和樹佈局之間切換檔案檢視。平面佈局在單個列表中顯示所有檔案路徑,樹佈局按目錄分組檔案。<br><br>可以在設定檔中使用 'gui.showFileTree' 鍵更改預設設定。 |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | 摺疊全部檔案 | 摺疊檔案樹中的全部目錄 |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | 展開全部檔案 | 展開檔案樹中的全部目錄 |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 收藏 (Stash) ## 收藏 (Stash)
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 套用 | Apply the stash entry to your working directory. | | `` <space> `` | 套用 | 將貯藏項應用到您的工作目錄。 |
| `` g `` | 還原 | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | 還原 | 將儲存項應用到工作目錄並刪除儲存項。 |
| `` d `` | 捨棄 | Remove the stash entry from the stash list. | | `` d `` | 捨棄 | 從貯藏列表中刪除該貯藏項。 |
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | 新分支 | 從選定的貯藏項建立一個新分支。這是透過 git 檢查建立貯藏項的提交,從該提交建立一個新分支,然後將貯藏項作為附加提交應用到新分支來實現的。 |
| `` w `` | 新建工作樹 | |
| `` r `` | 重新命名收藏 | | | `` r `` | 重新命名收藏 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 日誌 ## 日誌
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 本地分支 ## 本地分支
@ -284,17 +279,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` i `` | 顯示 git-flow 選項 | | | `` i `` | 顯示 git-flow 選項 | |
| `` <space> `` | 檢出 | 檢出選定的項目。 | | `` <space> `` | 檢出 | 檢出選定的項目。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` w `` | 新建工作樹 | |
| `` o `` | 建立拉取請求 | | | `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | | | `` O `` | 建立拉取請求選項 | |
| `` G `` | Open pull request in browser | | | `` G `` | 在瀏覽器中開啟拉取請求 | |
| `` <ctrl+y> `` | 複製拉取請求的 URL 到剪貼板 | | | `` <ctrl+y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | | `` c `` | 根據名稱檢出 | 按名稱檢出。在輸入框中,您可以輸入'-' 來切換到最後一個分支。 |
| `` - `` | Checkout previous branch | | | `` - `` | 簽出上一個分支 | |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | | `` F `` | 強制檢出 | 強制檢出所選分支。這將在檢出所選分支之前放棄工作目錄中的所有本地更改。 |
| `` d `` | 刪除 | View delete options for local/remote branch. | | `` d `` | 刪除 | 檢視本地/遠端分支的刪除選項。 |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | | `` r `` | 將已檢出的分支變基至此分支 | 將檢出的分支變基到所選的分支上。 |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | 合併到當前檢出的分支 | 檢視將選中項合併到目前分支的選項(正常合併,壓縮合並) |
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 | | `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
| `` T `` | 建立標籤 | | | `` T `` | 建立標籤 | |
| `` s `` | 排序規則 | | | `` s `` | 排序規則 | |
@ -302,25 +298,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 重新命名分支 | | | `` R `` | 重新命名分支 | |
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) | | `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 標籤 ## 標籤
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | 複製標籤到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected tag as a detached HEAD. | | `` <space> `` | 檢出 | 檢出選擇的標籤作為分離的HEAD。 |
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | 建立標籤 | 基於目前提交建立一個新標籤。您將在彈窗中輸入標籤名稱和描述(可選)。 |
| `` d `` | 刪除 | View delete options for local/remote tag. | | `` w `` | 新建工作樹 | |
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` d `` | 刪除 | 檢視本機/遠端標籤的刪除選項。 |
| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` P `` | 推送標籤 | 推送選擇的標籤到遠端。您將在彈窗中選擇一個遠端。 |
| `` g `` | 重設 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 檔案 ## 檔案
@ -328,53 +323,52 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | |
| `` <space> `` | 切換預存 | Toggle staged for selected file. | | `` <space> `` | 切換預存 | 切換所選檔案的暫存狀態。 |
| `` <ctrl+b> `` | 篩選檔案 (預存/未預存) | | | `` <ctrl+b> `` | 篩選檔案 (預存/未預存) | |
| `` y `` | 複製到剪貼簿 | | | `` y `` | 複製到剪貼簿 | |
| `` c `` | 提交變更 | 提交暫存區變更 | | `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | | | `` w `` | 沒有預提交 hook 就提交更改 | |
| `` A `` | 修改上次提交 | | | `` A `` | 修改上次提交 | |
| `` C `` | 使用 git 編輯器提交變更 | | | `` C `` | 使用 git 編輯器提交變更 | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | 尋找 fixup 的基礎提交 | 找出目前變更所依據的提交,以便 amend/fixup。這樣不必逐一檢視分支中的提交來找出要 amend/fixup 的提交。請見文件:<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 編輯 | 使用外部編輯器開啟 | | `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` i `` | 忽略或排除檔案 | | | `` i `` | 忽略或排除檔案 | |
| `` r `` | 重新整理檔案 | | | `` r `` | 重新整理檔案 | |
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. | | `` s `` | 收藏 | 貯藏所有變更.若要使用其他貯藏變體,請使用檢視貯藏選項快捷鍵。 |
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). | | `` S `` | 檢視收藏選項 | 檢視貯藏選項(例如:貯藏所有、貯藏已暫存變更、貯藏未暫存變更)。 |
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. | | `` a `` | 全部預存/取消預存 | 切換工作區中所有檔案的已暫存/未暫存狀態。 |
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | | `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | 如果選中的是一個檔案,則會進入到暫存檢視,以便可以暫存單個程式碼塊/行。如果選中的是一個目錄,則會摺疊/展開這個目錄。 |
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 | | `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
| `` g `` | 檢視遠端重設選項 | | | `` g `` | 檢視遠端重設選項 | |
| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). | | `` D `` | 重設 | 檢視工作樹的重置選項(例如:清除工作樹)。 |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | 顯示檔案樹狀視圖 | 在平面佈局和樹佈局之間切換檔案檢視。平面佈局在單個列表中顯示所有檔案路徑,樹佈局按目錄分組檔案。<br><br>可以在設定檔中使用 'gui.showFileTree' 鍵更改預設設定。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | 檢視合併衝突選項 | 檢視用於解決合併衝突的選項。 |
| `` f `` | 擷取 | 同步遠端異動 | | `` f `` | 擷取 | 同步遠端異動 |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | 摺疊全部檔案 | 摺疊檔案樹中的全部目錄 |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | 展開全部檔案 | 展開檔案樹中的全部目錄 |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 次要 ## 次要
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` <esc> `` | Exit back to side panel | | | `` <esc> `` | 退出回到側邊面板 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 狀態 ## 狀態
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` u `` | 檢查更新 | | | `` u `` | 檢查更新 | |
| `` <enter> `` | 切換到最近使用的版本庫 | | | `` <enter> `` | 切換到最近使用的版本庫 | |
| `` a `` | Show/cycle all branch logs | | | `` a `` | 顯示/迴圈所有分支日誌 | |
| `` A `` | Show/cycle all branch logs (reverse) | | | `` A `` | 顯示/迴圈所有分支日誌(反向) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
## 確認面板 ## 確認面板
@ -384,16 +378,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <esc> `` | 關閉/取消 | | | `` <esc> `` | 關閉/取消 | |
| `` <ctrl+o> `` | 複製到剪貼簿 | | | `` <ctrl+o> `` | 複製到剪貼簿 | |
## 輸入提示
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
## 遠端 ## 遠端
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | View branches | | | `` <enter> `` | 檢視分支 | |
| `` n `` | 新增遠端 | | | `` n `` | 新增遠端 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | | `` d `` | 刪除 | 刪除選中的遠端。從遠端跟蹤遠端分支的任何本地分支都不會受到影響。 |
| `` e `` | 編輯 | 編輯遠端 | | `` e `` | 編輯 | 編輯遠端 |
| `` f `` | 擷取 | 擷取遠端 | | `` f `` | 擷取 | 擷取遠端 |
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. | | `` F `` | 新增復刻遠端倉庫 | 透過替換 origin URL 中的所有者來快速新增復刻遠端倉庫,並可選擇從新遠端倉庫檢出分支。 |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 遠端分支 ## 遠端分支
@ -401,16 +402,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製分支名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製分支名稱到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | 檢出 | 基於目前選中的遠端分支檢出一個新的本地分支或者將遠端分支作分離的HEAD。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` w `` | 新建工作樹 | |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | | `` M `` | 合併到當前檢出的分支 | 檢視將選中項合併到目前分支的選項(正常合併,壓縮合並) |
| `` d `` | 刪除 | Delete the remote branch from the remote. | | `` r `` | 將已檢出的分支變基至此分支 | 將檢出的分支變基到所選的分支上。 |
| `` d `` | 刪除 | 從遠端刪除遠端分支。 |
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 | | `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
| `` s `` | 排序規則 | | | `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |

View file

@ -66,8 +66,8 @@ gui:
# The number of spaces per tab; used for everything that's shown in the main # The number of spaces per tab; used for everything that's shown in the main
# view, but probably mostly relevant for diffs. # view, but probably mostly relevant for diffs.
# Note that when using a pager, the pager has its own tab width setting, so you # Note that when using a diff renderer, the renderer has its own tab width
# need to pass it separately in the pager command. # setting, so you need to pass it separately in the renderer command.
tabWidth: 4 tabWidth: 4
# If true, capture mouse events. # If true, capture mouse events.
@ -110,6 +110,26 @@ gui:
# is true. # is true.
expandedSidePanelWeight: 2 expandedSidePanelWeight: 2
# If true, don't give a side panel more height than it needs to show its
# content; when all panels fit, the leftover height is shared among them so that
# they still fill the screen.
shrinkSidePanelsToContent: false
# The side panels, in the order they appear from top to bottom.
# Each entry is a list of one or more names that share a single panel as tabs
# (cycle through them with the next-tab/previous-tab keys).
# Omit a name to hide it; give a name its own one-element list to promote a tab
# to a top-level panel.
# Valid names are: 'status', 'files', 'worktrees', 'submodules', 'branches',
# 'remotes', 'tags', 'commits', 'reflog', 'stash'. 'files', 'branches', and
# 'commits' must always be included; they can't be hidden.
sidePanels:
- [status]
- [files, worktrees, submodules]
- [branches, remotes, tags]
- [commits, reflog]
- [stash]
# Sometimes the main window is split in two (e.g. when the selected file has # Sometimes the main window is split in two (e.g. when the selected file has
# both staged and unstaged changes). This setting controls how the two sections # both staged and unstaged changes). This setting controls how the two sections
# are split. # are split.
@ -316,13 +336,13 @@ gui:
spinner: spinner:
# The frames of the spinner animation. # The frames of the spinner animation.
frames: frames:
- '|' - ●∙∙
- / - ∙●∙
- '-' - ∙∙●
- \ - ∙●∙
# The "speed" of the spinner in milliseconds. # The "speed" of the spinner in milliseconds.
rate: 50 rate: 180
# Status panel view. # Status panel view.
# One of 'dashboard' (default) | 'allBranchesLog' # One of 'dashboard' (default) | 'allBranchesLog'
@ -340,30 +360,39 @@ gui:
# Config relating to git # Config relating to git
git: git:
# Array of pagers. Each entry has the following format: # Array of diff renderers. Each entry has the following format:
# #
# # Value of the --color arg in the git diff command. Some pagers want # # The type of diff renderer. One of: 'stdinFilter' (default) | 'extDiff'
# # this to be set to 'always' and some want it set to 'never' # # | 'rawGit'
# type: "stdinFilter"
#
# # A name for the diff renderer, shown in the notification when cycling
# # renderers. If not set, the name is derived from the first word of the
# # renderer command.
# name: ""
#
# # Value of the --color arg in the git diff command. Only used for type
# # 'stdinFilter'. Some renderers want this to be set to 'always' and some
# # want it set to 'never'.
# colorArg: "always" # colorArg: "always"
# #
# # The command to use for rendering diffs. This is either a stdinFilter or
# # an external diff command, depending on the type field; not applicable if
# # the type is 'rawGit'.
# # e.g. # # e.g.
# # diff-so-fancy # # diff-so-fancy
# # delta --dark --paging=never # # delta --dark --paging=never
# # ydiff -p cat -s --wrap --width={{columnWidth}} # # ydiff -p cat
# pager: "" # # difft --color=always
# command: ""
# #
# # e.g. 'difft --color=always' # # Extra arguments (array of strings) passed to the git command. Only
# externalDiffCommand: "" # # applicable if the type is 'rawGit'.
# args: []
# #
# # If true, Lazygit will use git's `diff.external` config for paging. # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_DiffRenderers.md
# # The advantage over `externalDiffCommand` is that this can be
# # configured per file type in .gitattributes; see
# # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
# useExternalDiffGitConfig: false
#
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
# for more information. # for more information.
pagers: [] diffRenderers: []
# Config relating to committing # Config relating to committing
commit: commit:
@ -406,6 +435,11 @@ git:
# If true, periodically refresh files and submodules # If true, periodically refresh files and submodules
autoRefresh: true autoRefresh: true
# If true, poll the repo periodically for external ref changes (commits, branch
# updates, checkouts made outside lazygit) and refresh when one is detected.
# Independent of autoRefresh, which only governs the files panel.
autoDetectExternalChanges: true
# If not "none", lazygit will automatically fast-forward local branches to match # If not "none", lazygit will automatically fast-forward local branches to match
# their upstream after fetching. Applies to branches that are not the currently # their upstream after fetching. Applies to branches that are not the currently
# checked out branch, and only to those that are strictly behind their upstream # checked out branch, and only to those that are strictly behind their upstream
@ -499,6 +533,15 @@ git:
# to 40 to disable truncation. # to 40 to disable truncation.
truncateCopiedCommitHashesTo: 12 truncateCopiedCommitHashesTo: 12
# Config relating to git worktrees
worktree:
# Default parent directory for new worktrees. It is offered as a candidate
# location alongside the parent directories of any worktrees you already have.
# A relative path is resolved against the repository's root directory, so
# "../worktrees" sits beside the repo and ".worktrees" sits inside it.
# A leading "~" is expanded to your home directory, so "~/worktrees" works.
defaultPath: ""
# Periodic update checks # Periodic update checks
update: update:
# One of: 'prompt' (default) | 'background' | 'never' # One of: 'prompt' (default) | 'background' | 'never'
@ -517,6 +560,11 @@ refresher:
# Auto-fetch can be disabled via option 'git.autoFetch'. # Auto-fetch can be disabled via option 'git.autoFetch'.
fetchInterval: 60 fetchInterval: 60
# Interval in seconds at which lazygit polls for external ref changes (commits,
# branch updates, checkouts made outside lazygit).
# Detection can be disabled via option 'git.autoDetectExternalChanges'.
externalChangeCheckInterval: 2
# If true, show a confirmation popup before quitting Lazygit # If true, show a confirmation popup before quitting Lazygit
confirmOnQuit: false confirmOnQuit: false
@ -648,6 +696,7 @@ keybinding:
confirmInEditor: [<ctrl+enter>, <ctrl+s>] confirmInEditor: [<ctrl+enter>, <ctrl+s>]
remove: d remove: d
new: "n" new: "n"
newWorktree: w
edit: e edit: e
openFile: o openFile: o
scrollUpMain: [<pgup>, K, <ctrl+u>] scrollUpMain: [<pgup>, K, <ctrl+u>]
@ -666,7 +715,8 @@ keybinding:
prevTab: '[' prevTab: '['
nextScreenMode: + nextScreenMode: +
prevScreenMode: _ prevScreenMode: _
cyclePagers: '|' cycleDiffRenderers: '|'
cycleDiffRenderersReverse: \
undo: z undo: z
redo: Z redo: Z
filteringMenu: <ctrl+s> filteringMenu: <ctrl+s>
@ -681,6 +731,7 @@ keybinding:
increaseRenameSimilarityThreshold: ) increaseRenameSimilarityThreshold: )
decreaseRenameSimilarityThreshold: ( decreaseRenameSimilarityThreshold: (
openDiffTool: <ctrl+t> openDiffTool: <ctrl+t>
editConfig: <alt+shift+c>
status: status:
checkForUpdate: u checkForUpdate: u
recentRepos: <enter> recentRepos: <enter>
@ -726,8 +777,6 @@ keybinding:
fetchRemote: f fetchRemote: f
addForkRemote: F addForkRemote: F
sortOrder: s sortOrder: s
worktrees:
viewWorktreeOptions: w
commits: commits:
squashDown: s squashDown: s
renameCommit: r renameCommit: r
@ -1059,6 +1108,12 @@ keybinding:
edit: <disabled> # disable 'edit file' edit: <disabled> # disable 'edit file'
``` ```
### Overriding the platform for default keybindings
A few keybindings have different defaults on macOS than on Linux and Windows (e.g. word-wise cursor movement in text inputs uses `alt` on macOS but `ctrl` elsewhere). Lazygit picks these based on the OS it's running on, but you can override that with the `LAZYGIT_KEYBINDING_PLATFORM` environment variable. Set it to `darwin`, `linux`, or `windows`; any other value is ignored and the actual OS is used.
This is useful when running lazygit in a Linux container that you access over ssh from a Mac, where you'd rather use the macOS keybindings.
### Example Keybindings For Colemak Users ### Example Keybindings For Colemak Users
```yaml ```yaml

View file

@ -0,0 +1,84 @@
# Custom Diff Renderers
Custom diff renderers are useful for showing a better rendering of a diff than git's builtin raw diff, and using one is strongly recommended (I personally prefer delta myself, but that's a matter of personal preference). There are three types of diff renderers that lazygit supports:
- **stdin filters**, e.g. [delta](#delta) and [diff-so-fancy](#diff-so-fancy). They take git's raw output as stdin and produce something nicer on stdout, and they are hooked up using git's GIT_PAGER mechanism. (These used to be called "custom pagers" in earlier lazygit versions.)
- **external diff programs**, e.g. difftastic; these are called using git's `--ext-diff` flag, and they take over diff generation from git completely rather than post-processing git's output.
- **git's raw output using custom arguments**; mainly useful for `--color-words` (or `--word-diff` if you are color blind).
Diff renderers are configured with the `diffRenderers` array in the `git` section of lazygit's config file; it is an array because you can have multiple entries that you can cycle through with the `|` key. This can be useful if you usually prefer a particular diff renderer, but want to use a different one for certain kinds of diffs.
Fields that are shared by all renderer types:
- **type** The type of diff renderer; choices are `stdinFilter`, `extDiff`, or `rawGit`. `stdinFilter` is the default, because it's the most common one; so you can omit this if you use delta.
- **name** A name that is shown in the status bar toast when cycling renderers; defaults to the first word of the renderer command, but can be useful e.g. to distinguish "delta" from "delta side-by-side" if you have entries for both.
Fields only for `stdinFilter`:
- **command** The command line to use for `GIT_PAGER`.
- **colorArg** whether you want the `--color=always` arg in your `git diff` command. Some diff renderers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most renderers need.
Fields only for `extDiff`:
- **command** The command line to use for the `diff.external` git config. If left empty, it uses the global value of git's `diff.external` config; this can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
You can include the `{{diffContext}}` template variable to pass lazygit's current diff context size (the value controlled by the `{`/`}` keybindings) to the diff tool.
Fields only for `rawGit`:
- **args** The additional arguments to use in the `git diff` or `git show` call (e.g. `--color-words`), as an array of strings.
Here's an example for a multi-renderer setup:
```yaml
git:
diffRenderers:
- command: delta --dark --paging=never
- command: ydiff -p cat
colorArg: never
- type: extDiff
command: difft --color=always --context={{diffContext}}
- type: rawGit
args: [--color-words]
name: color-words
- type: rawGit # git's default diff
name: default
```
## Delta:
```yaml
git:
diffRenderers:
- command: delta --dark --paging=never
```
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `command:` field to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
Note that delta's `--navigate` option doesn't work in lazygit, for technical reasons.
## Diff-so-fancy
```yaml
git:
diffRenderers:
- command: diff-so-fancy
```
![](https://i.imgur.com/rjH1TpT.png)
## ydiff
```yaml
gui:
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
git:
diffRenderers:
- colorArg: never
command: ydiff -p cat
```
![](https://i.imgur.com/vaa8z0H.png)

View file

@ -1,119 +0,0 @@
# Custom Pagers
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel).
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support. However, see [below](#emulating-custom-pagers-on-windows) for a workaround.
Multiple pagers are supported; you can cycle through them with the `|` key. This can be useful if you usually prefer a particular pager, but want to use a different one for certain kinds of diffs.
Pagers are configured with the `pagers` array in the git section; here's an example for a multi-pager setup (use an empty object `{}` for the default builtin diff display that doesn't use a pager):
```yaml
git:
pagers:
- pager: delta --dark --paging=never
- pager: ydiff -p cat -s --wrap --width={{columnWidth}}
colorArg: never
- externalDiffCommand: difft --color=always
- {} # default, no pager used
```
The `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most pagers need.
## Delta:
```yaml
git:
pagers:
- pager: delta --dark --paging=never
```
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
Note that delta's `--navigate` option doesn't work in lazygit, for technical reasons.
## Diff-so-fancy
```yaml
git:
pagers:
- pager: diff-so-fancy
```
![](https://i.imgur.com/rjH1TpT.png)
## ydiff
```yaml
gui:
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
git:
pagers:
- colorArg: never
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
```
![](https://i.imgur.com/vaa8z0H.png)
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
## Using external diff commands
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be
```yaml
git:
pagers:
- externalDiffCommand: difft --color=always
```
The `colorArg` and `pager` options are not used in this case.
You can add whatever extra arguments you prefer for your difftool; for instance
```yaml
git:
pagers:
- externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
```
Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using
```yaml
git:
pagers:
- useExternalDiffGitConfig: true
```
This can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
## Emulating custom pagers on Windows
There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as `lazygit-pager.ps1` at a convenient place on your disk:
```pwsh
#!/usr/bin/env pwsh
$old = $args[1].Replace('\', '/')
$new = $args[4].Replace('\', '/')
$path = $args[0]
git diff --no-index --no-ext-diff $old $new
| %{ $_.Replace($old, $path).Replace($new, $path) }
| delta --width=$env:LAZYGIT_COLUMNS
```
Use the pager of your choice with the arguments you like in the last line of the script. Personally I wouldn't want to use lazygit anymore without delta's `--hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"` args, see [above](#delta).
In your lazygit config, use
```yml
git:
pagers:
- externalDiffCommand: "C:/wherever/lazygit-pager.ps1"
```
The main limitation of this approach compared to a "real" pager is that renames are not displayed correctly; they are shown as if they were modifications of the old file. (This affects only the hunk headers; the diff itself is always correct.)

View file

@ -2,7 +2,7 @@
* [Configuration](./Config.md). * [Configuration](./Config.md).
* [Custom Commands](./Custom_Command_Keybindings.md) * [Custom Commands](./Custom_Command_Keybindings.md)
* [Custom Pagers](./Custom_Pagers.md) * [Custom Diff Renderers](./Custom_DiffRenderers.md)
* [Dev docs](./dev) * [Dev docs](./dev)
* [Keybindings](./keybindings) * [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md) * [Undo/Redo](./Undoing.md)

View file

@ -4,10 +4,14 @@
Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`. Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`.
We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit. In the commits view we don't filter, but search; this is deliberate because you typically care about the commits that come before/after a matching commit.
If you would like both filtering and searching to be enabled on a given view, please raise an issue for this. If you would like both filtering and searching to be enabled on a given view, please raise an issue for this.
## Menu filtering
The keybindings (`?`) and recent repositories menus can be filtered simply by typing. The filter field appears at the bottom of the menu while you type; there is no need to press `/` or confirm the filter before navigating the results.
## Filtering files by status ## Filtering files by status
You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view. You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view.

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Next screen mode (normal/half/fullscreen) | | | `` + `` | Next screen mode (normal/half/fullscreen) | |
| `` _ `` | Prev screen mode | | | `` _ `` | Prev screen mode | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Cancel | | | `` <esc> `` | Cancel | |
| `` ? `` | Open keybindings menu | | | `` ? `` | Open keybindings menu | |
| `` <ctrl+s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Quit | | | `` q, <ctrl+c> `` | Quit | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Edit config file | Open file in external editor. |
| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
@ -110,13 +112,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Confirmation panel ## Confirmation panel
@ -176,6 +178,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Checkout | Checkout selected item. | | `` <space> `` | Checkout | Checkout selected item. |
| `` n `` | New branch | | | `` n `` | New branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | Create pull request | | | `` o `` | Create pull request | |
| `` O `` | View create pull request options | | | `` O `` | View create pull request options | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -195,7 +198,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Main panel (merging) ## Main panel (merging)
@ -203,7 +205,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Pick hunk | | | `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Previous hunk | | | `` <up>, k `` | Previous hunk | |
| `` <down>, j `` | Next hunk | | | `` <down>, j `` | Next hunk | |
| `` <left>, h `` | Previous conflict | | | `` <left>, h `` | Previous conflict | |
@ -280,6 +282,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -287,7 +290,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
@ -297,6 +299,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy branch name to clipboard | | | `` <ctrl+o> `` | Copy branch name to clipboard | |
| `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | New branch | | | `` n `` | New branch | |
| `` w `` | New worktree | |
| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Delete | Delete the remote branch from the remote. |
@ -306,7 +309,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remotes ## Remotes
@ -337,17 +339,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Drop | Remove the stash entry from the stash list. | | `` d `` | Drop | Remove the stash entry from the stash list. |
| `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Rename stash | | | `` r `` | Rename stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Open config file | Open file in default application. |
| `` e `` | Edit config file | Open file in external editor. | | `` e `` | Edit config file | Open file in external editor. |
| `` u `` | Check for update | | | `` u `` | Check for update | |
| `` <enter> `` | Switch to a recent repo | | | `` <enter> `` | Switch to a recent repo | |
@ -365,6 +366,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -372,7 +374,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View files | | | `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Search the current view by text | | | `` / `` | Search the current view by text | |
## Submodules ## Submodules
@ -396,13 +397,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. |
| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | Delete | View delete options for local/remote tag. | | `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View commits | | | `` <enter> `` | View commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Worktrees ## Worktrees

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 更新 | Gitの状態を更新します`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 | | `` R `` | 更新 | Gitの状態を更新します`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
| `` + `` | 次の画面モード(通常/半分/全画面) | | | `` + `` | 次の画面モード(通常/半分/全画面) | |
| `` _ `` | 前の画面モード | | | `` _ `` | 前の画面モード | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | キャンセル | | | `` <esc> `` | キャンセル | |
| `` ? `` | キーバインディングメニューを開く | | | `` ? `` | キーバインディングメニューを開く | |
| `` <ctrl+s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 | | `` <ctrl+s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 終了 | | | `` q, <ctrl+c> `` | 終了 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | | `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 | | `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
@ -90,13 +92,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | | | `` / `` | 現在のビューをテキストで検索 | |
## コミットファイル ## コミットファイル
@ -136,6 +138,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | | `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
@ -143,7 +146,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | | | `` / `` | 現在のビューをテキストで検索 | |
## サブモジュール ## サブモジュール
@ -168,17 +170,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 | | `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 |
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 | | `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 | | `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
| `` w `` | 新しいワークツリー | |
| `` r `` | スタッシュの名前を変更 | | | `` r `` | スタッシュの名前を変更 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | | | `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ステータス ## ステータス
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 | | `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` u `` | 更新を確認 | | | `` u `` | 更新を確認 | |
| `` <enter> `` | 最近のリポジトリをチェックアウト | | | `` <enter> `` | 最近のリポジトリをチェックアウト | |
@ -201,13 +202,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | タグをクリップボードにコピー | | | `` <ctrl+o> `` | タグをクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 |
| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 | | `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 |
| `` w `` | 新しいワークツリー | |
| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 | | `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 |
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 | | `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ファイル ## ファイル
@ -286,7 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | ハンクを選択 | | | `` <space> `` | ハンクを選択 | |
| `` b `` | すべてのハンクを選択 | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 前のハンク | | | `` <up>, k `` | 前のハンク | |
| `` <down>, j `` | 次のハンク | | | `` <down>, j `` | 次のハンク | |
| `` <left>, h `` | 前のコンフリクト | | | `` <left>, h `` | 前のコンフリクト | |
@ -325,6 +326,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | ブラウザでコミットを開く | | | `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | | | `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します | | `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 | | `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | | | `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
@ -332,7 +334,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 現在のブランチのコミットを選択 | | | `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## リモート ## リモート
@ -354,6 +355,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | ブランチ名をクリップボードにコピー | | | `` <ctrl+o> `` | ブランチ名をクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 |
| `` n `` | 新しいブランチ | | | `` n `` | 新しいブランチ | |
| `` w `` | 新しいワークツリー | |
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) | | `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 | | `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
| `` d `` | 削除 | リモートからリモートブランチを削除します。 | | `` d `` | 削除 | リモートからリモートブランチを削除します。 |
@ -363,7 +365,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ローカルブランチ ## ローカルブランチ
@ -375,6 +376,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 | | `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
| `` n `` | 新しいブランチ | | | `` n `` | 新しいブランチ | |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | 新しいワークツリー | |
| `` o `` | プルリクエストを作成 | | | `` o `` | プルリクエストを作成 | |
| `` O `` | プルリクエスト作成オプションを表示 | | | `` O `` | プルリクエスト作成オプションを表示 | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -394,7 +396,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | | | `` <ctrl+t> `` | 外部差分ツールを開くgit difftool | |
| `` 0 `` | メインビューにフォーカス | | | `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | | | `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | | | `` / `` | 現在のビューをテキストでフィルタリング | |
## ワークツリー ## ワークツリー

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | | | `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | |
| `` _ `` | 이전 스크린 모드 | | | `` _ `` | 이전 스크린 모드 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | 취소 | | | `` <esc> `` | 취소 | |
| `` ? `` | 매뉴 열기 | | | `` ? `` | 매뉴 열기 | |
| `` <ctrl+s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 종료 | | | `` q, <ctrl+c> `` | 종료 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | 설정 파일 수정 | Open file in external editor. |
| `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
@ -67,6 +69,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | | | `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
@ -74,7 +77,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Secondary ## Secondary
@ -93,10 +95,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Drop | Remove the stash entry from the stash list. | | `` d `` | Drop | Remove the stash entry from the stash list. |
| `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Rename stash | | | `` r `` | Rename stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Sub-commits ## Sub-commits
@ -109,6 +111,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | | | `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
@ -116,7 +119,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## Worktrees ## Worktrees
@ -142,7 +144,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Pick hunk | | | `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 이전 hunk를 선택 | | | `` <up>, k `` | 이전 hunk를 선택 | |
| `` <down>, j `` | 다음 hunk를 선택 | | | `` <down>, j `` | 다음 hunk를 선택 | |
| `` <left>, h `` | 이전 충돌을 선택 | | | `` <left>, h `` | 이전 충돌을 선택 | |
@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | 체크아웃 | Checkout selected item. | | `` <space> `` | 체크아웃 | Checkout selected item. |
| `` n `` | 새 브랜치 생성 | | | `` n `` | 새 브랜치 생성 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | 풀 리퀘스트 생성 | | | `` o `` | 풀 리퀘스트 생성 | |
| `` O `` | 풀 리퀘스트 생성 옵션 | | | `` O `` | 풀 리퀘스트 생성 옵션 | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -229,14 +232,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 상태 ## 상태
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 설정 파일 열기 | Open file in default application. |
| `` e `` | 설정 파일 수정 | Open file in external editor. | | `` e `` | 설정 파일 수정 | Open file in external editor. |
| `` u `` | 업데이트 확인 | | | `` u `` | 업데이트 확인 | |
| `` <enter> `` | 최근에 사용한 저장소로 전환 | | | `` <enter> `` | 최근에 사용한 저장소로 전환 | |
@ -277,6 +278,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 브랜치명을 클립보드에 복사 | | | `` <ctrl+o> `` | 브랜치명을 클립보드에 복사 | |
| `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | 새 브랜치 생성 | | | `` n `` | 새 브랜치 생성 | |
| `` w `` | New worktree | |
| `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | 현재 브랜치에 병합 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | | `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | 삭제 | Delete the remote branch from the remote. | | `` d `` | 삭제 | Delete the remote branch from the remote. |
@ -286,7 +288,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 커밋 ## 커밋
@ -322,13 +323,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 브라우저에서 커밋 열기 | | | `` o `` | 브라우저에서 커밋 열기 | |
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | | `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | View selected item's files | | | `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | 검색 시작 | | | `` / `` | 검색 시작 | |
## 커밋 파일 ## 커밋 파일
@ -365,13 +366,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. | | `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. |
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | 삭제 | View delete options for local/remote tag. | | `` d `` | 삭제 | View delete options for local/remote tag. |
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | 커밋 보기 | | | `` <enter> `` | 커밋 보기 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## 파일 ## 파일

View file

@ -9,29 +9,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+r> `` | Wissel naar een recente repo | | | `` <ctrl+r> `` | Wissel naar een recente repo | |
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | | | `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | |
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | | | `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | |
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | | `` @ `` | Commandolog opties weergeven | Bekijk commandolog opties, bijv. commandolog tonen/verbergen en focus. |
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | | `` P `` | Push | Push de huidige branch naar de bijbehorende upstream-branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | | `` p `` | Pull | Pull wijzigingen van de remote voor de huidige branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | | `` : `` | Voer shellcommando uit | Bring up a prompt where you can enter a shell command to execute. |
| `` <ctrl+p> `` | Bekijk aangepaste patch opties | | | `` <ctrl+p> `` | Bekijk aangepaste patch opties | |
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. | | `` m `` | Bekijk merge/rebase opties | Toon abort/continue/skip opties voor huidige merge/rebase. |
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Volgende scherm modus (normaal/half/groot) | | | `` + `` | Volgende scherm modus (normaal/half/groot) | |
| `` _ `` | Vorige scherm modus | | | `` _ `` | Vorige scherm modus | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Annuleren | | | `` <esc> `` | Annuleren | |
| `` ? `` | Open menu | | | `` ? `` | Open menu | |
| `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | | `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q, <ctrl+c> `` | Quit | | | `` q, <ctrl+c> `` | Afsluiten | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Pauzeer de applicatie | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Witruimte weergeven in-/uitschakelen | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Verander config bestand | Open bestand in externe editor. |
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. | | `` Z `` | Redo (via reflog) (experimenteel) | Het reflog wordt gebruikt om te bepalen welk git commando moet worden gebruikt om het laatste git commando te herhalen. Wijzigingen aan de working tree worden niet meegenomen, alleen command's zijn kandidaten. |
## Lijstpaneel navigatie ## Lijstpaneel navigatie
@ -45,8 +47,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <shift+down> `` | Range select down | | | `` <shift+down> `` | Range select down | |
| `` <shift+up> `` | Range select up | | | `` <shift+up> `` | Range select up | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
| `` H `` | Scroll left | | | `` H `` | Scroll naar links | |
| `` L `` | Scroll right | | | `` L `` | Scroll naar rechts | |
| `` ] `` | Volgende tabblad | | | `` ] `` | Volgende tabblad | |
| `` [ `` | Vorige tabblad | | | `` [ `` | Vorige tabblad | |
@ -56,15 +58,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | | | `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
| `` <space> `` | Toggle staged | Toggle staged for selected file. | | `` <space> `` | Toggle staged | Toggle staged for selected file. |
| `` <ctrl+b> `` | Filter files by status | | | `` <ctrl+b> `` | Filter bestanden op status | |
| `` y `` | Copy to clipboard | | | `` y `` | Kopieer naar klembord | |
| `` c `` | Commit veranderingen | Commit staged changes. | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
| `` w `` | Commit veranderingen zonder pre-commit hook | | | `` w `` | Commit veranderingen zonder pre-commit hook | |
| `` A `` | Wijzig laatste commit | | | `` A `` | Wijzig laatste commit | |
| `` C `` | Commit veranderingen met de git editor | | | `` C `` | Commit veranderingen met de git editor | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | Edit | Open file in external editor. | | `` e `` | Edit | Open bestand in externe editor. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` i `` | Ignore or exclude file | | | `` i `` | Ignore or exclude file | |
| `` r `` | Refresh bestanden | | | `` r `` | Refresh bestanden | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | | `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
@ -73,13 +75,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <enter> `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | | `` <enter> `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. | | `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. |
| `` g `` | Bekijk upstream reset opties | | | `` g `` | Bekijk upstream reset opties | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | | `` D `` | Resetten | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
| `` f `` | Fetch | Fetch changes from remote. | | `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
@ -89,7 +91,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | Bevestig | | | `` <enter> `` | Bevestig | |
| `` <esc> `` | Sluiten | | | `` <esc> `` | Sluiten | |
| `` <ctrl+o> `` | Copy to clipboard | | | `` <ctrl+o> `` | Kopieer naar klembord | |
## Branches ## Branches
@ -97,18 +99,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer branch name naar klembord | | | `` <ctrl+o> `` | Kopieer branch name naar klembord | |
| `` i `` | Laat git-flow opties zien | | | `` i `` | Laat git-flow opties zien | |
| `` <space> `` | Uitchecken | Checkout selected item. | | `` <space> `` | Uitchecken | Geselecteerd item uitchecken. |
| `` n `` | Nieuwe branch | | | `` n `` | Nieuwe branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` o `` | Maak een pull-request | | | `` o `` | Maak een pull-request | |
| `` O `` | Bekijk opties voor pull-aanvraag | | | `` O `` | Bekijk opties voor pull-aanvraag | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
| `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | | | `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | |
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | | `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
| `` - `` | Checkout previous branch | | | `` - `` | Vorige branch uitchecken | |
| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | | `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. | | `` d `` | Verwijderen | View delete options for local/remote branch. |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. | | `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. |
| `` T `` | Creëer tag | | | `` T `` | Creëer tag | |
@ -116,10 +119,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Bekijk reset opties | | | `` g `` | Bekijk reset opties | |
| `` R `` | Hernoem branch | | | `` R `` | Hernoem branch | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | | `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Commit bericht ## Commit bericht
@ -134,18 +136,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | | | `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
| `` y `` | Copy to clipboard | | | `` y `` | Kopieer naar klembord | |
| `` c `` | Uitchecken | Bestand uitchecken | | `` c `` | Uitchecken | Bestand uitchecken |
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand | | `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Edit | Open file in external editor. | | `` e `` | Edit | Open bestand in externe editor. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
@ -159,36 +161,36 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | | `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
| `` r `` | Hernoem commit | Reword the selected commit's message. | | `` r `` | Hernoem commit | Herschrijf de commit message van de geselecteerde commit. |
| `` R `` | Hernoem commit met editor | | | `` R `` | Hernoem commit met editor | |
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | | `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | Wijzig commit | | `` e `` | Bewerken (start interactieve rebase) | Wijzig commit |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. | | `` i `` | Start interactieve rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | Kies commit (wanneer midden in rebase) | | `` p `` | Pick | Kies commit (wanneer midden in rebase) |
| `` F `` | Creëer fixup commit | Creëer fixup commit | | `` F `` | Creëer fixup commit | Creëer fixup commit |
| `` S `` | Apply fixup commits | Squash bovenstaande commits | | `` S `` | Apply fixup commits | Squash bovenstaande commits |
| `` <ctrl+j>, <alt+down> `` | Verplaats commit 1 naar beneden | | | `` <ctrl+j>, <alt+down> `` | Verplaats commit 1 naar beneden | |
| `` <ctrl+k>, <alt+up> `` | Verplaats commit 1 naar boven | | | `` <ctrl+k>, <alt+up> `` | Verplaats commit 1 naar boven | |
| `` V `` | Plak commits (cherry-pick) | | | `` V `` | Plak commits (cherry-pick) | |
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | | `` B `` | Markeer als basiscommit voor rebase | Selecteer een basiscommit voor de volgende rebase. Als je rebased op een branch worden alleen commits boven de basiscommit meegenomen. Hiervoor wordt het `git rebase --onto` commando gebruikt. |
| `` A `` | Amend | Wijzig commit met staged veranderingen | | `` A `` | Amend | Wijzig commit met staged veranderingen |
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | | `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | | `` t `` | Revert | Maak een revert commit voor de geselecteerde commit, die de wijzigingen in deze commit terugdraait. |
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | | `` T `` | Tag commit | Maak een nieuwe tag die naar de geselecteerde commit wijst. Je wordt gevraagd om een tag naam en optionele omschrijving. |
| `` <ctrl+l> `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | | `` <ctrl+l> `` | Log opties weergeven | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Input prompt ## Input prompt
@ -211,15 +213,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Kies stuk | | | `` <space> `` | Kies stuk | |
| `` b `` | Kies beide stukken | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Selecteer bovenste hunk | | | `` <up>, k `` | Selecteer bovenste hunk | |
| `` <down>, j `` | Selecteer onderste hunk | | | `` <down>, j `` | Selecteer onderste hunk | |
| `` <left>, h `` | Selecteer voorgaand conflict | | | `` <left>, h `` | Selecteer voorgaand conflict | |
| `` <right>, l `` | Selecteer volgende conflict | | | `` <right>, l `` | Selecteer volgende conflict | |
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. | | `` z `` | Ongedaan maken | Undo last merge conflict resolution. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
| `` <esc> `` | Ga terug naar het bestanden paneel | | | `` <esc> `` | Ga terug naar het bestanden paneel | |
## Normaal ## Normaal
@ -239,10 +241,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | Selecteer de vorige hunk | | | `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | | | `` <right>, l `` | Selecteer de volgende hunk | |
| `` v `` | Toggle drag selecteer | | | `` v `` | Toggle drag selecteer | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | Wissel tussen hunk selectie aan of uit | Wissel tussen regel-voor-regel of hunk selectie modus. |
| `` <ctrl+o> `` | Copy selected text to clipboard | | | `` <ctrl+o> `` | Copy selected text to clipboard | |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | | | `` <space> `` | Voeg toe/verwijder lijn(en) in patch | |
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
| `` <esc> `` | Sluit lijn-bij-lijn modus | | | `` <esc> `` | Sluit lijn-bij-lijn modus | |
@ -253,19 +255,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | | | `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
@ -273,27 +275,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer branch name naar klembord | | | `` <ctrl+o> `` | Kopieer branch name naar klembord | |
| `` <space> `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Uitchecken | Geselecteerde remote branch uitchecken als nieuwe locale branch of als detached head. |
| `` n `` | Nieuwe branch | | | `` n `` | Nieuwe branch | |
| `` w `` | New worktree | |
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | | `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Verwijderen | Verwijder de remote branch van de remote. |
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch | | `` u `` | Instellen als upstream | Stel in als upstream van uitgecheckte branch |
| `` s `` | Sort order | | | `` s `` | Sort order | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Remotes ## Remotes
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | View branches | | | `` <enter> `` | Bekijk branches | |
| `` n `` | Voeg een nieuwe remote toe | | | `` n `` | Voeg een nieuwe remote toe | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | | `` d `` | Verwijderen | Verwijder de geselecteerde remote. Locale branches die een branch tracken van de remote worden niet aangepast. |
| `` e `` | Edit | Wijzig remote | | `` e `` | Edit | Wijzig remote |
| `` f `` | Fetch | Fetch remote | | `` f `` | Fetch | Fetch remote |
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. | | `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. |
@ -314,19 +316,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | Selecteer de vorige hunk | | | `` <left>, h `` | Selecteer de vorige hunk | |
| `` <right>, l `` | Selecteer de volgende hunk | | | `` <right>, l `` | Selecteer de volgende hunk | |
| `` v `` | Toggle drag selecteer | | | `` v `` | Toggle drag selecteer | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | Wissel tussen hunk selectie aan of uit | Wissel tussen regel-voor-regel of hunk selectie modus. |
| `` <ctrl+o> `` | Copy selected text to clipboard | | | `` <ctrl+o> `` | Copy selected text to clipboard | |
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged | | `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | | `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | Open bestand | Open file in default application. | | `` o `` | Open bestand | Open bestand in standaardapplicatie. |
| `` e `` | Verander bestand | Open file in external editor. | | `` e `` | Verander bestand | Open bestand in externe editor. |
| `` <esc> `` | Ga terug naar het bestanden paneel | | | `` <esc> `` | Ga terug naar het bestanden paneel | |
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | | `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. | | `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | Commit veranderingen | Commit staged changes. | | `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
| `` w `` | Commit veranderingen zonder pre-commit hook | | | `` w `` | Commit veranderingen zonder pre-commit hook | |
| `` C `` | Commit veranderingen met de git editor | | | `` C `` | Commit veranderingen met de git editor | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Stash ## Stash
@ -337,18 +339,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Laten vallen | Remove the stash entry from the stash list. | | `` d `` | Laten vallen | Remove the stash entry from the stash list. |
| `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` r `` | Rename stash | | | `` w `` | New worktree | |
| `` r `` | Hernoem stash | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Open config bestand | Open file in default application. | | `` e `` | Verander config bestand | Open bestand in externe editor. |
| `` e `` | Verander config bestand | Open file in external editor. |
| `` u `` | Check voor updates | | | `` u `` | Check voor updates | |
| `` <enter> `` | Wissel naar een recente repo | | | `` <enter> `` | Wissel naar een recente repo | |
| `` a `` | Show/cycle all branch logs | | | `` a `` | Show/cycle all branch logs | |
@ -360,19 +361,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | | `` <space> `` | Uitchecken | Check de geselecteerde branch uit als een detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | | | `` o `` | Open commit in browser | |
| `` n `` | Creëer nieuwe branch van commit | | | `` n `` | Creëer nieuwe branch van commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
| `` w `` | New worktree | |
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | | | `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk gecommite bestanden | | | `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | | | `` / `` | Start met zoeken | |
## Submodules ## Submodules
@ -381,7 +382,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Kopieer submodule naam naar klembord | | | `` <ctrl+o> `` | Kopieer submodule naam naar klembord | |
| `` <enter> `` | Enter | Enter submodule | | `` <enter> `` | Enter | Enter submodule |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | | `` d `` | Verwijderen | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | Update selected submodule. | | `` u `` | Update | Update selected submodule. |
| `` n `` | Voeg nieuwe submodule toe | | | `` n `` | Voeg nieuwe submodule toe | |
| `` e `` | Update submodule URL | | | `` e `` | Update submodule URL | |
@ -394,15 +395,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Uitchecken | Geselecteerde tag uitchecken als detached HEAD. |
| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | Creëer tag | Maak een nieuwe tag die naar de huidige commit wijst. Je wordt gevraagd om een tag naam en optionele beschrijving. |
| `` d `` | Delete | View delete options for local/remote tag. | | `` w `` | New worktree | |
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` d `` | Verwijderen | View delete options for local/remote tag. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` P `` | Tag pushen | Push de geselecteerde tag naar een remote. Je krijgt de optie een remote te selecteren. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` g `` | Resetten | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Bekijk commits | | | `` <enter> `` | Bekijk commits | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Worktrees ## Worktrees
@ -411,6 +412,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` n `` | New worktree | | | `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. | | `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | | | `` o `` | Openen in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | | `` d `` | Verwijderen | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. | | `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | | | `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | |
| `` _ `` | Poprzedni tryb ekranu | | | `` _ `` | Poprzedni tryb ekranu | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Anuluj | | | `` <esc> `` | Anuluj | |
| `` ? `` | Otwórz menu przypisań klawiszy | | | `` ? `` | Otwórz menu przypisań klawiszy | |
| `` <ctrl+s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. | | `` <ctrl+s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Wyjdź | | | `` q, <ctrl+c> `` | Wyjdź | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | | `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. | | `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
@ -83,13 +85,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Dodatkowy ## Dodatkowy
@ -120,6 +122,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | | | `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
@ -127,7 +130,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Główny panel (budowanie łatki) ## Główny panel (budowanie łatki)
@ -162,6 +164,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Przełącz | Przełącz wybrany element. | | `` <space> `` | Przełącz | Przełącz wybrany element. |
| `` n `` | Nowa gałąź | | | `` n `` | Nowa gałąź | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` o `` | Utwórz żądanie ściągnięcia | | | `` o `` | Utwórz żądanie ściągnięcia | |
| `` O `` | Zobacz opcje tworzenia pull requesta | | | `` O `` | Zobacz opcje tworzenia pull requesta | |
| `` G `` | Otwórz żądanie ściągnięcia w przeglądarce | | | `` G `` | Otwórz żądanie ściągnięcia w przeglądarce | |
@ -181,7 +184,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Menu ## Menu
@ -207,7 +209,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Wybierz fragment | | | `` <space> `` | Wybierz fragment | |
| `` b `` | Wybierz wszystkie fragmenty | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Poprzedni fragment | | | `` <up>, k `` | Poprzedni fragment | |
| `` <down>, j `` | Następny fragment | | | `` <down>, j `` | Następny fragment | |
| `` <left>, h `` | Poprzedni konflikt | | | `` <left>, h `` | Poprzedni konflikt | |
@ -316,17 +318,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. | | `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. |
| `` d `` | Usuń | Usuń wpis schowka z listy schowka. | | `` d `` | Usuń | Usuń wpis schowka z listy schowka. |
| `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. | | `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. |
| `` w `` | Nowe drzewo pracy | |
| `` r `` | Zmień nazwę schowka | | | `` r `` | Zmień nazwę schowka | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Otwórz plik konfiguracyjny | Otwórz plik w domyślnej aplikacji. |
| `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. | | `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
| `` u `` | Sprawdź aktualizacje | | | `` u `` | Sprawdź aktualizacje | |
| `` <enter> `` | Przełącz na ostatnie repozytorium | | | `` <enter> `` | Przełącz na ostatnie repozytorium | |
@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Otwórz commit w przeglądarce | | | `` o `` | Otwórz commit w przeglądarce | |
| `` n `` | Utwórz nową gałąź z commita | | | `` n `` | Utwórz nową gałąź z commita | |
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nowe drzewo pracy | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | | `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | | | `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
@ -351,7 +353,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Wyświetl pliki | | | `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Szukaj w bieżącym widoku po tekście | | | `` / `` | Szukaj w bieżącym widoku po tekście | |
## Submoduły ## Submoduły
@ -375,13 +376,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Skopiuj tag do schowka | | | `` <ctrl+o> `` | Skopiuj tag do schowka | |
| `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). | | `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). |
| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | | `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. |
| `` w `` | Nowe drzewo pracy | |
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. | | `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. |
| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. | | `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | | `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |
## Zdalne ## Zdalne
@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Kopiuj nazwę gałęzi do schowka | | | `` <ctrl+o> `` | Kopiuj nazwę gałęzi do schowka | |
| `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. | | `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. |
| `` n `` | Nowa gałąź | | | `` n `` | Nowa gałąź | |
| `` w `` | Nowe drzewo pracy | |
| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. | | `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. |
| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. | | `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. |
| `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. | | `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. |
@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | | `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Pokaż commity | | | `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | | | `` / `` | Filtruj bieżący widok po tekście | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. | | `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. |
| `` + `` | Modo de tela seguinte (normal/metade/tela cheia) | | | `` + `` | Modo de tela seguinte (normal/metade/tela cheia) | |
| `` _ `` | Modo de tela anterior | | | `` _ `` | Modo de tela anterior | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Cancelar | | | `` <esc> `` | Cancelar | |
| `` ? `` | Abrir o menu de atalhos do teclado | | | `` ? `` | Abrir o menu de atalhos do teclado | |
| `` <ctrl+s> `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Sair | | | `` q, <ctrl+c> `` | Sair | |
| `` <ctrl+z> `` | Suspender a aplicação | | | `` <ctrl+z> `` | Suspender a aplicação | |
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | | `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. | | `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
@ -92,6 +94,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Verificar | Checar item selecionado | | `` <space> `` | Verificar | Checar item selecionado |
| `` n `` | Nova branch | | | `` n `` | Nova branch | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` o `` | Criar solicitação de pull | | | `` o `` | Criar solicitação de pull | |
| `` O `` | View create pull request options | | | `` O `` | View create pull request options | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Branches remotos ## Branches remotos
@ -121,6 +123,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copiar nome da branch para área de transferência | | | `` <ctrl+o> `` | Copiar nome da branch para área de transferência | |
| `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado | | `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado |
| `` n `` | Nova branch | | | `` n `` | Nova branch | |
| `` w `` | Nova árvore de trabalho | |
| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) | | `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) |
| `` r `` | Refazer | Refazer a branch checada na branch selecionada | | `` r `` | Refazer | Refazer a branch checada na branch selecionada |
| `` d `` | Apagar | Excluir o branch remoto do controle remoto. | | `` d `` | Apagar | Excluir o branch remoto do controle remoto. |
@ -130,7 +133,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Commit arquivos ## Commit arquivos
@ -186,13 +188,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Pesquisar na visualização atual por texto | | | `` / `` | Pesquisar na visualização atual por texto | |
## Etiquetas ## Etiquetas
@ -202,13 +204,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copiar etiqueta para área de transferência | | | `` <ctrl+o> `` | Copiar etiqueta para área de transferência | |
| `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado | | `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado |
| `` n `` | Nova etiqueta | Crie uma nova etiqueta a partir do commit atual. Você será solicitado a digitar um nome e uma descrição opcional. | | `` n `` | Nova etiqueta | Crie uma nova etiqueta a partir do commit atual. Você será solicitado a digitar um nome e uma descrição opcional. |
| `` w `` | Nova árvore de trabalho | |
| `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. | | `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. |
| `` P `` | Empurrar etiqueta | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Empurrar etiqueta | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | | | `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Input prompt ## Input prompt
@ -271,7 +273,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Escolha o local | | | `` <space> `` | Escolha o local | |
| `` b `` | Pegar todos os pedaços | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Trecho anterior | | | `` <up>, k `` | Trecho anterior | |
| `` <down>, j `` | Próximo trecho | | | `` <down>, j `` | Próximo trecho | |
| `` <left>, h `` | Conflito anterior | | | `` <left>, h `` | Conflito anterior | |
@ -308,6 +310,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -315,7 +318,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver commits | | | `` <enter> `` | Ver commits | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Remotes ## Remotes
@ -346,17 +348,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. | | `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. |
| `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. | | `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. |
| `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. | | `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. |
| `` w `` | Nova árvore de trabalho | |
| `` r `` | Renomear o stash | | | `` r `` | Renomear o stash | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Filtrar a visualização atual por texto | | | `` / `` | Filtrar a visualização atual por texto | |
## Status ## Status
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Abrir o ficheiro de config | Abrir arquivo no aplicativo padrão. |
| `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. | | `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
| `` u `` | Verificar atualização | | | `` u `` | Verificar atualização | |
| `` <enter> `` | Mudar para um repositório recente | | | `` <enter> `` | Mudar para um repositório recente | |
@ -374,6 +375,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Abrir commit no navegador | | | `` o `` | Abrir commit no navegador | |
| `` n `` | Create new branch off of commit | | | `` n `` | Create new branch off of commit | |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | Nova árvore de trabalho | |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. | | `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. | | `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | | | `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
@ -381,7 +383,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focar visualização principal | | | `` 0 `` | Focar visualização principal | |
| `` <enter> `` | Ver arquivos | | | `` <enter> `` | Ver arquivos | |
| `` w `` | Ver opções da árvore de trabalho | |
| `` / `` | Pesquisar na visualização atual por texto | | | `` / `` | Pesquisar na visualização atual por texto | |
## Submódulos ## Submódulos

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | | | `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | |
| `` _ `` | Предыдущий режим экрана | | | `` _ `` | Предыдущий режим экрана | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | Отменить | | | `` <esc> `` | Отменить | |
| `` ? `` | Открыть меню | | | `` ? `` | Открыть меню | |
| `` <ctrl+s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | Выйти | | | `` q, <ctrl+c> `` | Выйти | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | Suspend the application | |
| `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` <alt+shift+c> `` | Редактировать файл конфигурации | Open file in external editor. |
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | | `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. | | `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
@ -112,7 +114,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | Выбрать эту часть | | | `` <space> `` | Выбрать эту часть | |
| `` b `` | Выбрать все части | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | Выбрать предыдущую часть | | | `` <up>, k `` | Выбрать предыдущую часть | |
| `` <down>, j `` | Выбрать следующую часть | | | `` <down>, j `` | Выбрать следующую часть | |
| `` <left>, h `` | Выбрать предыдущий конфликт | | | `` <left>, h `` | Выбрать предыдущий конфликт | |
@ -149,6 +151,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | | | `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
@ -156,7 +159,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Коммиты ## Коммиты
@ -192,13 +194,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Найти | | | `` / `` | Найти | |
## Локальные Ветки ## Локальные Ветки
@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | Переключить | Checkout selected item. | | `` <space> `` | Переключить | Checkout selected item. |
| `` n `` | Новая ветка | | | `` n `` | Новая ветка | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` o `` | Создать запрос на принятие изменений | | | `` o `` | Создать запрос на принятие изменений | |
| `` O `` | Создать параметры запроса принятие изменений | | | `` O `` | Создать параметры запроса принятие изменений | |
| `` G `` | Open pull request in browser | | | `` G `` | Open pull request in browser | |
@ -229,7 +232,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Меню ## Меню
@ -258,6 +260,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | Открыть коммит в браузере | | | `` o `` | Открыть коммит в браузере | |
| `` n `` | Создать новую ветку с этого коммита | | | `` n `` | Создать новую ветку с этого коммита | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` w `` | New worktree | |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | | | `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
@ -265,7 +268,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | Select commits of current branch | | | `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Найти | | | `` / `` | Найти | |
## Подмодули ## Подмодули
@ -313,7 +315,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | Открыть файл конфигурации | Open file in default application. |
| `` e `` | Редактировать файл конфигурации | Open file in external editor. | | `` e `` | Редактировать файл конфигурации | Open file in external editor. |
| `` u `` | Проверить обновления | | | `` u `` | Проверить обновления | |
| `` <enter> `` | Переключиться на последний репозиторий | | | `` <enter> `` | Переключиться на последний репозиторий | |
@ -328,13 +329,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | Copy tag to clipboard | |
| `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. | | `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. |
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` w `` | New worktree | |
| `` d `` | Delete | View delete options for local/remote tag. | | `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Удалённые ветки ## Удалённые ветки
@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | Скопировать название ветки в буфер обмена | | | `` <ctrl+o> `` | Скопировать название ветки в буфер обмена | |
| `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` n `` | Новая ветка | | | `` n `` | Новая ветка | |
| `` w `` | New worktree | |
| `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | Слияние с текущей переключённой веткой | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | | `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. | | `` d `` | Delete | Delete the remote branch from the remote. |
@ -353,7 +355,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | Open external diff tool (git difftool) | | | `` <ctrl+t> `` | Open external diff tool (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть коммиты | | | `` <enter> `` | Просмотреть коммиты | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |
## Удалённые репозитории ## Удалённые репозитории
@ -409,8 +410,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. | | `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. |
| `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` w `` | New worktree | |
| `` r `` | Переименовать хранилище | | | `` r `` | Переименовать хранилище | |
| `` 0 `` | Focus main view | | | `` 0 `` | Focus main view | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | | | `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | | | `` / `` | Filter the current view by text | |

View file

@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 刷新 | 刷新Git状态即在后台运行`git status`、`git branch`等命令以更新面板内容)。此操作不会执行`git fetch`。 | | `` R `` | 刷新 | 刷新Git状态即在后台运行`git status`、`git branch`等命令以更新面板内容)。此操作不会执行`git fetch`。 |
| `` + `` | 下一屏模式(正常/半屏/全屏) | | | `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | | | `` _ `` | 上一屏模式 | |
| `` \| `` | 切换分页器 | 从已配置的分页器列表中选择下一个分页器 | | `` \| `` | Cycle diff renderers | Choose the next renderer in the list of configured diff renderers. |
| `` \ `` | Cycle diff renderers (reverse) | Choose the previous renderer in the list of configured diff renderers. |
| `` <esc> `` | 取消 | | | `` <esc> `` | 取消 | |
| `` ? `` | 打开菜单 | | | `` ? `` | 打开菜单 | |
| `` <ctrl+s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 | | `` <ctrl+s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` q, <ctrl+c> `` | 退出 | | | `` q, <ctrl+c> `` | 退出 | |
| `` <ctrl+z> `` | 挂起应用程序 | | | `` <ctrl+z> `` | 挂起应用程序 | |
| `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 | | `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 |
| `` <alt+shift+c> `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 | | `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 |
| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 | | `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 |
@ -60,6 +62,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | | | `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
@ -67,7 +70,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 子模块 ## 子模块
@ -104,6 +106,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | | | `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 提交 ## 提交
@ -147,13 +149,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` o `` | 在浏览器中打开提交 | | | `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | | | `` n `` | 从提交创建新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 | | `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` * `` | 选择当前分支的提交 | | | `` * `` | 选择当前分支的提交 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | | | `` / `` | 开始搜索 | |
## 提交信息 ## 提交信息
@ -225,6 +227,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <space> `` | 检出 | 检出选中的项目 | | `` <space> `` | 检出 | 检出选中的项目 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 | | `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
| `` w `` | 新建工作树 | |
| `` o `` | 创建拉取请求 | | | `` o `` | 创建拉取请求 | |
| `` O `` | 创建拉取请求选项 | | | `` O `` | 创建拉取请求选项 | |
| `` G `` | 在浏览器中打开拉取请求 | | | `` G `` | 在浏览器中打开拉取请求 | |
@ -244,7 +247,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 构建补丁中 ## 构建补丁中
@ -270,13 +272,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 复制标签到剪贴板 | | | `` <ctrl+o> `` | 复制标签到剪贴板 | |
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD | | `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 | | `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 |
| `` w `` | 新建工作树 | |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 | | `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 | | `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 | | `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 次要 ## 次要
@ -292,7 +294,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 选中区块 | | | `` <space> `` | 选中区块 | |
| `` b `` | 选中所有区块 | | | `` b `` | Pick both hunks | |
| `` <up>, k `` | 选择顶部块 | | | `` <up>, k `` | 选择顶部块 | |
| `` <down>, j `` | 选择底部块 | | | `` <down>, j `` | 选择底部块 | |
| `` <left>, h `` | 选择上一个冲突 | | | `` <left>, h `` | 选择上一个冲突 | |
@ -339,7 +341,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 | | `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` u `` | 检查更新 | | | `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | | | `` <enter> `` | 切换到最近的仓库 | |
@ -371,10 +372,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 | | `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 | | `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 | | `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
| `` w `` | 新建工作树 | |
| `` r `` | 重命名贮藏 | | | `` r `` | 重命名贮藏 | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交的文件 | | | `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |
## 输入提示 ## 输入提示
@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 复制分支名称到剪贴板 | | | `` <ctrl+o> `` | 复制分支名称到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 | | `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` w `` | 新建工作树 | |
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) | | `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 | | `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 | | `` d `` | 删除 | 从远程删除远程分支。 |
@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | | | `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | 聚焦主视图 | | | `` 0 `` | 聚焦主视图 | |
| `` <enter> `` | 查看提交 | | | `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | | | `` / `` | 通过文本过滤当前视图 | |

View file

@ -9,27 +9,29 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+r> `` | 切換到最近使用的版本庫 | | | `` <ctrl+r> `` | 切換到最近使用的版本庫 | |
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | 向上捲動主面板 | | | `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | 向上捲動主面板 | |
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | 向下捲動主面板 | | | `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | 向下捲動主面板 | |
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. | | `` @ `` | 開啟命令記錄選單 | 檢視命令日誌的選項,例如顯示/隱藏命令日誌以及聚焦命令日誌。 |
| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 | | `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 |
| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 | | `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ) `` | 提高重新命名相似度閾值 | 提高將刪除和新增對視為重新命名所需的相似度閾值。<br><br>預設值可在設定檔中透過鍵 'git.renameSimilarityThreshold' 更改。 |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | 降低重新命名相似度閾值 | 降低將刪除和新增對視為重新命名所需的相似度閾值。<br><br>預設值可在設定檔中透過鍵 'git.renameSimilarityThreshold' 更改。 |
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | 增加差異檢視中變更周圍顯示的上下文量。<br><br>預設值可在設定檔中透過鍵 'git.diffContextSize' 更改。 |
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. | | `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | 減少差異檢視中變更周圍顯示的上下文量。<br><br>預設值可在設定檔中透過鍵 'git.diffContextSize' 更改。 |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. | | `` : `` | 執行 Shell 命令 | 調出可輸入shell命令執行的提示符。 |
| `` <ctrl+p> `` | 檢視自訂補丁選項 | | | `` <ctrl+p> `` | 檢視自訂補丁選項 | |
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. | | `` m `` | 查看合併/變基選項 | 檢視目前合併或變基的中止、繼續、跳過選項。 |
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | | `` R `` | 重新整理 | 重新整理Git狀態即在背景執行`git status`、`git branch`等命令以更新面板內容)。此操作不會執行`git fetch`。 |
| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | | | `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | |
| `` _ `` | 上一個螢幕模式 | | | `` _ `` | 上一個螢幕模式 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers | | `` \| `` | 切換差異渲染器 | 選擇已設定的差異渲染器清單中的下一個渲染器。 |
| `` \ `` | 切換差異渲染器(反向) | 選擇已設定的差異渲染器清單中的上一個渲染器。 |
| `` <esc> `` | 取消 | | | `` <esc> `` | 取消 | |
| `` ? `` | 開啟選單 | | | `` ? `` | 開啟選單 | |
| `` <ctrl+s> `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. | | `` <ctrl+s> `` | 檢視篩選路徑選項 | 檢視用於過濾提交日誌的選項,以便僅顯示與過濾器匹配的提交。 |
| `` W, <ctrl+e> `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | | `` W, <ctrl+e> `` | 開啟差異比較選單 | 檢視與比較兩個引用相關的選項,例如與選定的 ref 進行比較,輸入要比較的 ref然後反轉比較方向。 |
| `` q, <ctrl+c> `` | 結束 | | | `` q, <ctrl+c> `` | 結束 | |
| `` <ctrl+z> `` | Suspend the application | | | `` <ctrl+z> `` | 掛起應用程式 | |
| `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. | | `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | 切換是否在差異檢視中顯示空白字元更改。<br><br>預設值可在設定檔中透過鍵 'git.ignoreWhitespaceInDiffView' 更改。 |
| `` <alt+shift+c> `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 | | `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 | | `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
@ -42,21 +44,14 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <, <home> `` | 捲動到頂部 | | | `` <, <home> `` | 捲動到頂部 | |
| `` >, <end> `` | 捲動到底部 | | | `` >, <end> `` | 捲動到底部 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` <shift+down> `` | Range select down | | | `` <shift+down> `` | 向下擴充套件選擇範圍 | |
| `` <shift+up> `` | Range select up | | | `` <shift+up> `` | 向上擴充套件選擇範圍 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
| `` H `` | 向左捲動 | | | `` H `` | 向左捲動 | |
| `` L `` | 向右捲動 | | | `` L `` | 向右捲動 | |
| `` ] `` | 下一個索引標籤 | | | `` ] `` | 下一個索引標籤 | |
| `` [ `` | 上一個索引標籤 | | | `` [ `` | 上一個索引標籤 | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
## 主面板 (補丁生成) ## 主面板 (補丁生成)
| Key | Action | Info | | Key | Action | Info |
@ -64,12 +59,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | 選擇上一段 | | | `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | | | `` <right>, l `` | 選擇下一段 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | 切換程式碼塊選擇 | 切換逐行選擇與程式碼塊選擇模式。 |
| `` <ctrl+o> `` | 複製所選文本至剪貼簿 | | | `` <ctrl+o> `` | 複製所選文本至剪貼簿 | |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | | | `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. | | `` d `` | 從提交中移除行 | 從本次提交中移除所選行。此操作會在背景執行互動式變基,因此如果後續提交也修改了這些行,您可能會遇到合併衝突。 |
| `` <esc> `` | 退出自訂補丁建立器 | | | `` <esc> `` | 退出自訂補丁建立器 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
@ -79,8 +74,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|-----|--------|-------------| |-----|--------|-------------|
| `` <mouse wheel down> (fn+up) `` | 向下捲動 | | | `` <mouse wheel down> (fn+up) `` | 向下捲動 | |
| `` <mouse wheel up> (fn+down) `` | 向上捲動 | | | `` <mouse wheel up> (fn+down) `` | 向上捲動 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` <esc> `` | Exit back to side panel | | | `` <esc> `` | 退出回到側邊面板 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 主面板(合併) ## 主面板(合併)
@ -88,15 +83,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 挑選程式碼片段 | | | `` <space> `` | 挑選程式碼片段 | |
| `` b `` | 挑選所有程式碼片段 | | | `` b `` | 選取兩個區塊 | |
| `` <up>, k `` | 選擇上一段 | | | `` <up>, k `` | 選擇上一段 | |
| `` <down>, j `` | 選擇下一段 | | | `` <down>, j `` | 選擇下一段 | |
| `` <left>, h `` | 選擇上一個衝突 | | | `` <left>, h `` | 選擇上一個衝突 | |
| `` <right>, l `` | 選擇下一個衝突 | | | `` <right>, l `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. | | `` z `` | 復原 | 撤消上次合併衝突解決。 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | 檢視合併衝突選項 | 檢視用於解決合併衝突的選項。 |
| `` <esc> `` | 返回檔案面板 | | | `` <esc> `` | 返回檔案面板 | |
## 主面板(預存) ## 主面板(預存)
@ -106,19 +101,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <left>, h `` | 選擇上一段 | | | `` <left>, h `` | 選擇上一段 | |
| `` <right>, l `` | 選擇下一段 | | | `` <right>, l `` | 選擇下一段 | |
| `` v `` | 切換拖曳選擇 | | | `` v `` | 切換拖曳選擇 | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. | | `` a `` | 切換程式碼塊選擇 | 切換逐行選擇與程式碼塊選擇模式。 |
| `` <ctrl+o> `` | 複製所選文本至剪貼簿 | | | `` <ctrl+o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) | | `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | | `` d `` | 刪除變更 (git reset) | 選取未暫存的變更時,使用 `git reset` 捨棄變更。選取已暫存的變更時,取消暫存變更。 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <esc> `` | 返回檔案面板 | | | `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. | | `` E `` | 編輯程式碼塊 | 在外部編輯器中編輯選中的程式碼塊。 |
| `` c `` | 提交變更 | 提交暫存區變更 | | `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | | | `` w `` | 沒有預提交 hook 就提交更改 | |
| `` C `` | 使用 git 編輯器提交變更 | | | `` C `` | 使用 git 編輯器提交變更 | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | 尋找 fixup 的基礎提交 | 找出目前變更所依據的提交,以便 amend/fixup。這樣不必逐一檢視分支中的提交來找出要 amend/fixup 的提交。請見文件:<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 功能表 ## 功能表
@ -133,20 +128,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 子模組 ## 子模組
@ -154,12 +149,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製子模組名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製子模組名稱到剪貼簿 | |
| `` <enter> `` | Enter | 進入子模組 | | `` <enter> `` | 進入 | 進入子模組 |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | | `` d `` | 刪除 | 刪除選定的子模組及其相應的目錄。 |
| `` u `` | Update | 更新子模組 | | `` u `` | 更新 | 更新子模組 |
| `` n `` | 新增子模組 | | | `` n `` | 新增子模組 | |
| `` e `` | 更新子模組 URL | | | `` e `` | 更新子模組 URL | |
| `` i `` | Initialize | 初始化子模組 | | `` i `` | 初始化 | 初始化子模組 |
| `` b `` | 查看批量子模組選項 | | | `` b `` | 查看批量子模組選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
@ -167,27 +162,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` n `` | New worktree | | | `` n `` | 新建工作樹 | |
| `` <space> `` | Switch | Switch to the selected worktree. | | `` <space> `` | 切換 | 切換到選中的工作樹。 |
| `` o `` | 在編輯器中開啟 | | | `` o `` | 在編輯器中開啟 | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. | | `` d `` | 刪除 | 刪除選定的工作樹。這將刪除工作樹的目錄以及 .git 目錄中有關工作樹的後設資料。 |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 提交 ## 提交
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` b `` | 查看二分選項 | | | `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | | `` s `` | 壓縮 (Squash) | 將已選提交壓縮到該提交之下。這些選定的提交的訊息會附加到該提交的訊息之下。 |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | | `` f `` | 修復 (Fixup) | 將選定的提交合併到其下面的提交中。與壓縮類似,但所選提交的訊息將被丟棄。 |
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. | | `` c `` | 設定修復提交資訊 | 設定修復提交的資訊選項。-C 選項表示使用此提交的資訊,而非目標提交的資訊。 |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 | | `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | | | `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | | `` d `` | 刪除提交 | 刪除選中的提交。這將透過變基從分支中刪除該提交,如果該提交修改的內容依賴於後續的提交,則需要解決合併衝突。 |
| `` e `` | 編輯(開始互動變基) | 編輯提交 | | `` e `` | 編輯(開始互動變基) | 編輯提交 |
| `` i `` | 開始互動變基 | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.<br>If you would instead like to start an interactive rebase from the selected commit, press `e`. | | `` i `` | 開始互動變基 | 為分支上的提交啟動互動式變基。這將包括從 HEAD 提交到第一個合併提交或主分支提交的所有提交。<br>如果您想從所選提交啟動互動式變基,請按 `e` |
| `` p `` | 挑選 | 挑選提交 (於變基過程中) | | `` p `` | 挑選 | 挑選提交 (於變基過程中) |
| `` F `` | 建立修復提交 | 為此提交建立修復提交 | | `` F `` | 建立修復提交 | 為此提交建立修復提交 |
| `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 | | `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 |
@ -196,23 +191,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` V `` | 貼上提交 (揀選) | | | `` V `` | 貼上提交 (揀選) | |
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 | | `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
| `` A `` | 修改 | 使用已預存的更改修正提交 | | `` A `` | 修改 | 使用已預存的更改修正提交 |
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. | | `` a `` | 設定/重設提交作者 | 設定或重置提交的作者,或新增其他作者。 |
| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | | `` t `` | 還原 | 為所選提交建立還原提交,這會反向應用所選提交的更改。 |
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | | `` T `` | 打標籤到提交 | 建立一個新標籤指向所選提交。您可以在彈窗中輸入標籤名稱和描述(可選)。 |
| `` <ctrl+l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | | `` <ctrl+l> `` | 開啟記錄選單 | 檢視提交日誌的選項,例如更改排序順序、隱藏 git graph、顯示整個 git graph。 |
| `` G `` | Open pull request in browser | | | `` G `` | 在瀏覽器中開啟拉取請求 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 提交摘要 ## 提交摘要
@ -229,51 +224,51 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | |
| `` y `` | 複製到剪貼簿 | | | `` y `` | 複製到剪貼簿 | |
| `` c `` | 檢出 | 檢出檔案 | | `` c `` | 檢出 | 檢出檔案 |
| `` d `` | 捨棄 | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. | | `` d `` | 捨棄 | 放棄對此檔案的提交變更。 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯 | 使用外部編輯器開啟 | | `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` <space> `` | 切換檔案是否包含在補丁中 | 切換檔案是否包含在自定義補丁中。請參閱 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | | `` a `` | 切換所有檔案是否包含在補丁中 | 新增或刪除所有提交中的檔案到自定義的補丁中。請參閱 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | | `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | 如果已選擇一個檔案則Enter進入該檔案以便您可以向自定義補丁新增/刪除單獨的行。如果選擇了目錄,則切換目錄。 |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | 顯示檔案樹狀視圖 | 在平面佈局和樹佈局之間切換檔案檢視。平面佈局在單個列表中顯示所有檔案路徑,樹佈局按目錄分組檔案。<br><br>可以在設定檔中使用 'gui.showFileTree' 鍵更改預設設定。 |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | 摺疊全部檔案 | 摺疊檔案樹中的全部目錄 |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | 展開全部檔案 | 展開檔案樹中的全部目錄 |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 收藏 (Stash) ## 收藏 (Stash)
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <space> `` | 套用 | Apply the stash entry to your working directory. | | `` <space> `` | 套用 | 將貯藏項應用到您的工作目錄。 |
| `` g `` | 還原 | Apply the stash entry to your working directory and remove the stash entry. | | `` g `` | 還原 | 將儲存項應用到工作目錄並刪除儲存項。 |
| `` d `` | 捨棄 | Remove the stash entry from the stash list. | | `` d `` | 捨棄 | 從貯藏列表中刪除該貯藏項。 |
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | | `` n `` | 新分支 | 從選定的貯藏項建立一個新分支。這是透過 git 檢查建立貯藏項的提交,從該提交建立一個新分支,然後將貯藏項作為附加提交應用到新分支來實現的。 |
| `` w `` | 新建工作樹 | |
| `` r `` | 重新命名收藏 | | | `` r `` | 重新命名收藏 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視所選項目的檔案 | | | `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 日誌 ## 日誌
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy abbreviated commit hash to clipboard | | | `` <ctrl+o> `` | 複製縮略提交雜湊值到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | | `` <space> `` | 檢出 | 檢出所選擇的提交作為分離HEAD。 |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | | `` y `` | 複製提交屬性 | 複製提交屬性到剪貼簿(如hash、URL、diff、訊息、作者)。 |
| `` o `` | 在瀏覽器中開啟提交 | | | `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | | | `` n `` | 從提交建立新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` w `` | 新建工作樹 | |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` C `` | 複製提交 (揀選) | 標記提交為已複製。然後,在本地提交檢視中,您可以按 `V` (Cherry-Pick) 將已複製的提交貼上到已檢出的分支中。任何時候都可以按 `<esc>` 來取消選擇。 |
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | | | `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` * `` | Select commits of current branch | | | `` * `` | 選擇目前分支的提交 | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 本地分支 ## 本地分支
@ -284,17 +279,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` i `` | 顯示 git-flow 選項 | | | `` i `` | 顯示 git-flow 選項 | |
| `` <space> `` | 檢出 | 檢出選定的項目。 | | `` <space> `` | 檢出 | 檢出選定的項目。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). | | `` N `` | 移動提交至新分支 | 建立一個新分支,並將目前分支未推送的提交移動到該分支。如果您打算開始新工作但忘記先建立新分支,這會很有用。<br><br>請注意,此操作忽略選擇,新分支總是從主分支建立或堆疊在目前分支之上(您可以選擇哪種方式)。 |
| `` w `` | 新建工作樹 | |
| `` o `` | 建立拉取請求 | | | `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | | | `` O `` | 建立拉取請求選項 | |
| `` G `` | Open pull request in browser | | | `` G `` | 在瀏覽器中開啟拉取請求 | |
| `` <ctrl+y> `` | 複製拉取請求的 URL 到剪貼板 | | | `` <ctrl+y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the previous branch. | | `` c `` | 根據名稱檢出 | 按名稱檢出。在輸入框中,您可以輸入'-' 來切換到最後一個分支。 |
| `` - `` | Checkout previous branch | | | `` - `` | 簽出上一個分支 | |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | | `` F `` | 強制檢出 | 強制檢出所選分支。這將在檢出所選分支之前放棄工作目錄中的所有本地更改。 |
| `` d `` | 刪除 | View delete options for local/remote branch. | | `` d `` | 刪除 | 檢視本地/遠端分支的刪除選項。 |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | | `` r `` | 將已檢出的分支變基至此分支 | 將檢出的分支變基到所選的分支上。 |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` M `` | 合併到當前檢出的分支 | 檢視將選中項合併到目前分支的選項(正常合併,壓縮合並) |
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 | | `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
| `` T `` | 建立標籤 | | | `` T `` | 建立標籤 | |
| `` s `` | 排序規則 | | | `` s `` | 排序規則 | |
@ -302,25 +298,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` R `` | 重新命名分支 | | | `` R `` | 重新命名分支 | |
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) | | `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 標籤 ## 標籤
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | Copy tag to clipboard | | | `` <ctrl+o> `` | 複製標籤到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected tag as a detached HEAD. | | `` <space> `` | 檢出 | 檢出選擇的標籤作為分離的HEAD。 |
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | | `` n `` | 建立標籤 | 基於目前提交建立一個新標籤。您將在彈窗中輸入標籤名稱和描述(可選)。 |
| `` d `` | 刪除 | View delete options for local/remote tag. | | `` w `` | 新建工作樹 | |
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. | | `` d `` | 刪除 | 檢視本機/遠端標籤的刪除選項。 |
| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` P `` | 推送標籤 | 推送選擇的標籤到遠端。您將在彈窗中選擇一個遠端。 |
| `` g `` | 重設 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 檔案 ## 檔案
@ -328,53 +323,52 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製檔案名稱到剪貼簿 | |
| `` <space> `` | 切換預存 | Toggle staged for selected file. | | `` <space> `` | 切換預存 | 切換所選檔案的暫存狀態。 |
| `` <ctrl+b> `` | 篩選檔案 (預存/未預存) | | | `` <ctrl+b> `` | 篩選檔案 (預存/未預存) | |
| `` y `` | 複製到剪貼簿 | | | `` y `` | 複製到剪貼簿 | |
| `` c `` | 提交變更 | 提交暫存區變更 | | `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | | | `` w `` | 沒有預提交 hook 就提交更改 | |
| `` A `` | 修改上次提交 | | | `` A `` | 修改上次提交 | |
| `` C `` | 使用 git 編輯器提交變更 | | | `` C `` | 使用 git 編輯器提交變更 | |
| `` <ctrl+f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | | `` <ctrl+f> `` | 尋找 fixup 的基礎提交 | 找出目前變更所依據的提交,以便 amend/fixup。這樣不必逐一檢視分支中的提交來找出要 amend/fixup 的提交。請見文件:<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 編輯 | 使用外部編輯器開啟 | | `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 | | `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` i `` | 忽略或排除檔案 | | | `` i `` | 忽略或排除檔案 | |
| `` r `` | 重新整理檔案 | | | `` r `` | 重新整理檔案 | |
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. | | `` s `` | 收藏 | 貯藏所有變更.若要使用其他貯藏變體,請使用檢視貯藏選項快捷鍵。 |
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). | | `` S `` | 檢視收藏選項 | 檢視貯藏選項(例如:貯藏所有、貯藏已暫存變更、貯藏未暫存變更)。 |
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. | | `` a `` | 全部預存/取消預存 | 切換工作區中所有檔案的已暫存/未暫存狀態。 |
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | | `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | 如果選中的是一個檔案,則會進入到暫存檢視,以便可以暫存單個程式碼塊/行。如果選中的是一個目錄,則會摺疊/展開這個目錄。 |
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 | | `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
| `` g `` | 檢視遠端重設選項 | | | `` g `` | 檢視遠端重設選項 | |
| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). | | `` D `` | 重設 | 檢視工作樹的重置選項(例如:清除工作樹)。 |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. | | `` ` `` | 顯示檔案樹狀視圖 | 在平面佈局和樹佈局之間切換檔案檢視。平面佈局在單個列表中顯示所有檔案路徑,樹佈局按目錄分組檔案。<br><br>可以在設定檔中使用 'gui.showFileTree' 鍵更改預設設定。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. | | `` M `` | 檢視合併衝突選項 | 檢視用於解決合併衝突的選項。 |
| `` f `` | 擷取 | 同步遠端異動 | | `` f `` | 擷取 | 同步遠端異動 |
| `` - `` | Collapse all files | Collapse all directories in the files tree | | `` - `` | 摺疊全部檔案 | 摺疊檔案樹中的全部目錄 |
| `` = `` | Expand all files | Expand all directories in the file tree | | `` = `` | 展開全部檔案 | 展開檔案樹中的全部目錄 |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 次要 ## 次要
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). | | `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | 切換到其他檢視(已暫存/未暫存的變更)。 |
| `` <esc> `` | Exit back to side panel | | | `` <esc> `` | 退出回到側邊面板 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 狀態 ## 狀態
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 | | `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` u `` | 檢查更新 | | | `` u `` | 檢查更新 | |
| `` <enter> `` | 切換到最近使用的版本庫 | | | `` <enter> `` | 切換到最近使用的版本庫 | |
| `` a `` | Show/cycle all branch logs | | | `` a `` | 顯示/迴圈所有分支日誌 | |
| `` A `` | Show/cycle all branch logs (reverse) | | | `` A `` | 顯示/迴圈所有分支日誌(反向) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
## 確認面板 ## 確認面板
@ -384,16 +378,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| `` <esc> `` | 關閉/取消 | | | `` <esc> `` | 關閉/取消 | |
| `` <ctrl+o> `` | 複製到剪貼簿 | | | `` <ctrl+o> `` | 複製到剪貼簿 | |
## 輸入提示
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
## 遠端 ## 遠端
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <enter> `` | View branches | | | `` <enter> `` | 檢視分支 | |
| `` n `` | 新增遠端 | | | `` n `` | 新增遠端 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | | `` d `` | 刪除 | 刪除選中的遠端。從遠端跟蹤遠端分支的任何本地分支都不會受到影響。 |
| `` e `` | 編輯 | 編輯遠端 | | `` e `` | 編輯 | 編輯遠端 |
| `` f `` | 擷取 | 擷取遠端 | | `` f `` | 擷取 | 擷取遠端 |
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. | | `` F `` | 新增復刻遠端倉庫 | 透過替換 origin URL 中的所有者來快速新增復刻遠端倉庫,並可選擇從新遠端倉庫檢出分支。 |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |
## 遠端分支 ## 遠端分支
@ -401,16 +402,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info | | Key | Action | Info |
|-----|--------|-------------| |-----|--------|-------------|
| `` <ctrl+o> `` | 複製分支名稱到剪貼簿 | | | `` <ctrl+o> `` | 複製分支名稱到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | | `` <space> `` | 檢出 | 基於目前選中的遠端分支檢出一個新的本地分支或者將遠端分支作分離的HEAD。 |
| `` n `` | 新分支 | | | `` n `` | 新分支 | |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) | | `` w `` | 新建工作樹 | |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | | `` M `` | 合併到當前檢出的分支 | 檢視將選中項合併到目前分支的選項(正常合併,壓縮合並) |
| `` d `` | 刪除 | Delete the remote branch from the remote. | | `` r `` | 將已檢出的分支變基至此分支 | 將檢出的分支變基到所選的分支上。 |
| `` d `` | 刪除 | 從遠端刪除遠端分支。 |
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 | | `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
| `` s `` | 排序規則 | | | `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | | `` g `` | 檢視重設選項 | 檢視重置選項 (soft/mixed/hard) 用於重置到選擇項。 |
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | | | `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
| `` 0 `` | Focus main view | | | `` 0 `` | 聚焦主檢視 | |
| `` <enter> `` | 檢視提交 | | | `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | | | `` / `` | 搜尋 | |

View file

@ -7,7 +7,7 @@
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69, "revCount": 69,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz?rev=ff81ac966bb2cae68946d5ed5fc4994f96d0ffec&revCount=69"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@ -19,11 +19,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1759362264, "lastModified": 1785627969,
"narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=", "narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "758cf7296bee11f1706a574c77d072b8a7baa881", "rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1759831965, "lastModified": 1785828668,
"narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", "narHash": "sha256-8fsyqeO+mJqvIzeO4xIpgJe/f7MTbbVTEC6RT6WSXNs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c9b6fb798541223bbb396d287d16f43520250518", "rev": "e72e4f299401a3689d4b3d5fc6496b11db7064eb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -50,11 +50,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1754788789, "lastModified": 1785031560,
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=", "narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6", "rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -65,11 +65,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1754340878, "lastModified": 1770107345,
"narHash": "sha256-lgmUyVQL9tSnvvIvBp7x1euhkkCho7n3TMzgjdvgPoU=", "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "cab778239e705082fe97bb4990e0d24c50924c04", "rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -108,11 +108,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1758728421, "lastModified": 1785360170,
"narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=", "narHash": "sha256-XE1lKgQ3eIO3E7zWryqcRsax+mYXod/5RHBn4YaR9YE=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1", "rev": "d1187f8bc71fb8aab02395869ec3f5c1920f75c0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -101,6 +101,7 @@
# Development tools # Development tools
git git
gnumake gnumake
just
]; ];
# Environment variables for development # Environment variables for development
@ -108,8 +109,8 @@
}; };
treefmt = { treefmt = {
programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt-rfc-style.compiler; programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt.compiler;
programs.nixfmt.package = pkgs.nixfmt-rfc-style; programs.nixfmt.package = pkgs.nixfmt;
programs.gofmt.enable = true; programs.gofmt.enable = true;
}; };

39
go.mod
View file

@ -5,6 +5,9 @@ go 1.25.0
// This is necessary to ignore test files when executing gofumpt. // This is necessary to ignore test files when executing gofumpt.
ignore ./test ignore ./test
// Likewise for worktrees that are nested in the main tree.
ignore ./.worktrees
require ( require (
dario.cat/mergo v1.0.2 dario.cat/mergo v1.0.2
github.com/adrg/xdg v0.5.3 github.com/adrg/xdg v0.5.3
@ -13,7 +16,7 @@ require (
github.com/cli/go-gh/v2 v2.13.0 github.com/cli/go-gh/v2 v2.13.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.24 github.com/creack/pty v1.1.24
github.com/gdamore/tcell/v3 v3.4.0 github.com/gdamore/tcell/v3 v3.4.2
github.com/go-errors/errors v1.5.1 github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.6.1 github.com/gookit/color v1.6.1
github.com/integrii/flaggy v1.8.0 github.com/integrii/flaggy v1.8.0
@ -21,24 +24,25 @@ require (
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3 github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
github.com/kyokomi/emoji/v2 v2.2.13 github.com/kyokomi/emoji/v2 v2.2.14
github.com/lucasb-eyer/go-colorful v1.4.0 github.com/lucasb-eyer/go-colorful v1.4.1
github.com/mgutz/str v1.2.0 github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/go-ps v1.0.0
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
github.com/sahilm/fuzzy v0.1.2 github.com/sahilm/fuzzy v0.1.3
github.com/samber/lo v1.53.0 github.com/samber/lo v1.53.0
github.com/sanity-io/litter v1.5.8 github.com/sanity-io/litter v1.5.8
github.com/sasha-s/go-deadlock v0.3.9 github.com/sasha-s/go-deadlock v0.3.9
github.com/sirupsen/logrus v1.9.4 github.com/sirupsen/logrus v1.10.2
github.com/spf13/afero v1.15.0 github.com/spf13/afero v1.15.0
github.com/spkg/bom v1.0.1 github.com/spkg/bom v1.0.1
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304 github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.12.1
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e github.com/xo/terminfo v1.0.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sync v0.20.0 golang.org/x/sync v0.22.0
golang.org/x/sys v0.45.0 golang.org/x/sys v0.47.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -49,28 +53,27 @@ require (
github.com/cli/safeexec v1.0.1 // indirect github.com/cli/safeexec v1.0.1 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hpcloud/tail v1.0.0 // indirect github.com/hpcloud/tail v1.0.0 // indirect
github.com/invopop/jsonschema v0.10.0 // indirect github.com/invopop/jsonschema v0.10.0 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.34.1 // indirect github.com/onsi/gomega v1.34.1 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/net v0.47.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/term v0.43.0 // indirect golang.org/x/mod v0.38.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/tools v0.48.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
mvdan.cc/gofumpt v0.11.0 // indirect
) )
tool mvdan.cc/gofumpt

68
go.sum
View file

@ -21,25 +21,24 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v3 v3.4.0 h1:VUym1HQZiYodA5PGQrqLxF7QwqQndcAUwQD7G7XUy5E= github.com/gdamore/tcell/v3 v3.4.2 h1:gGW+6z2Bz5Wl2mNwFlm9+eRmg2JQrWcKjSkL1LRfpNU=
github.com/gdamore/tcell/v3 v3.4.0/go.mod h1:fjKxNiIFwbzTxDU+i+AAMz+xPOgXVaZq5tbShsKseHc= github.com/gdamore/tcell/v3 v3.4.2/go.mod h1:Oe5U3S3jm3NzypswDNUhe+LUnF5CoFq2b4sepD++QHo=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
@ -72,10 +71,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U= github.com/kyokomi/emoji/v2 v2.2.14 h1:YOF6VL52613M0Qr9v4puJDD9QQPmyyjXedDDlrGzH80=
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/kyokomi/emoji/v2 v2.2.14/go.mod h1:1AnYl9IgmJZXKd5m1PEijyyUw85SqYsuAr8lpU/s+9s=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@ -98,25 +97,22 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sahilm/fuzzy v0.1.3 h1:juByESSS32nVD81vr6tHmKmA/8zde7gE+x5CLxrzXPU=
github.com/sahilm/fuzzy v0.1.2 h1:kdSkz23lx1meNjEl+SLJULeSbjTI4Dn14K/YxdGrIww= github.com/sahilm/fuzzy v0.1.3/go.mod h1:au6//VbVSqu6DFrkL2CfjlJ5iURpNCPeE+1GwY3XsT8=
github.com/sahilm/fuzzy v0.1.2/go.mod h1:au6//VbVSqu6DFrkL2CfjlJ5iURpNCPeE+1GwY3XsT8=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg= github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w= github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w=
github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y= github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spkg/bom v1.0.1 h1:tl8kQ2sufL/wDEJa9me1jnQYEpDB7LqYGNkwCVR5GLs= github.com/spkg/bom v1.0.1 h1:tl8kQ2sufL/wDEJa9me1jnQYEpDB7LqYGNkwCVR5GLs=
@ -126,31 +122,35 @@ github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304/go.
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -162,24 +162,26 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -193,3 +195,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc=
mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo=

View file

@ -22,29 +22,50 @@ unit-test:
go test ./... -short go test ./... -short
# Run both unit tests and integration tests. # Run both unit tests and integration tests.
test: unit-test e2e-all [unix]
test: unit-test e2e
# On Windows, integration tests are not supported right now
[windows]
test: unit-test
# Generate all our auto-generated files (test list, cheatsheets, json schema, maybe other things in the future) # Generate all our auto-generated files (test list, cheatsheets, json schema, maybe other things in the future)
generate: generate:
go generate ./... go generate ./...
format: format:
gofumpt -l -w . go tool gofumpt -l -w .
lint: lint:
./scripts/gofumpt-check.sh
./scripts/golangci-lint-shim.sh run ./scripts/golangci-lint-shim.sh run
# Run integration tests with a visible UI. Most useful for running a single test; for running all tests, use `e2e-all` instead. e2e-test-command := "go test -timeout 30m pkg/integration/clients/*.go"
# Run integration tests headlessly: no args runs all tests, a test name (or path) runs just that one. Use e2e-cli for a visible UI.
e2e *args: e2e *args:
{{ if args == "" { e2e-test-command } else { \
e2e-test-command + " -run 'TestIntegration/" + \
replace( \
replace_regex( \
replace_regex(args, '\S*pkg/integration/tests/', ''), \
'\.go( |$)', '${1}' \
), \
" ", "$' && " + e2e-test-command + " -run 'TestIntegration/" \
) + "$'" \
} }}
# Run a single integration test with a visible UI; most useful with --sandbox or --slow.
e2e-cli *args:
go run cmd/integration_test/main.go cli {{ args }} go run cmd/integration_test/main.go cli {{ args }}
# Open the TUI for running integration tests. # Open the TUI for running integration tests.
e2e-tui *args: e2e-tui *args:
go run cmd/integration_test/main.go tui {{ args }} go run cmd/integration_test/main.go tui {{ args }}
# Run all integration tests headlessly (without a visible UI). # Run some tests on the current commit, similar to what CI does.
e2e-all: check:
go test pkg/integration/clients/*.go ./scripts/check_commit.sh
bump-gocui: bump-gocui:
scripts/bump_gocui.sh scripts/bump_gocui.sh
@ -54,4 +75,4 @@ demo *args:
demo/record_demo.sh {{ args }} demo/record_demo.sh {{ args }}
vendor: vendor:
go mod vendor && go mod tidy go mod tidy && go mod vendor

View file

@ -14,6 +14,7 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types" appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands/direnv"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
@ -171,6 +172,17 @@ func openRecentRepo(app *App) bool {
for _, repoDir := range app.Config.GetAppState().RecentRepos { for _, repoDir := range app.Config.GetAppState().RecentRepos {
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo { if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
if err := os.Chdir(repoDir); err == nil { if err := os.Chdir(repoDir); err == nil {
// We're still in setup, before the gui exists, so we can't show the approval popup
// that DispatchSwitchTo offers for blocked .envrc files; just log and move on.
// Also, the logs only go to the debug log, not the Command Log, because that's not
// available yet, either.
result := direnv.Load(app.OSCommand.Cmd)
if result.Message != "" {
app.Log.WithField("message", result.Message).Info("direnv")
}
if result.Err != nil {
app.Log.WithError(result.Err).Warn("direnv load failed")
}
return true return true
} }
} }
@ -239,12 +251,8 @@ func (app *App) setupRepo(
} }
// check if we have a recent repo we can open // check if we have a recent repo we can open
for _, repoDir := range app.Config.GetAppState().RecentRepos { if openRecentRepo(app) {
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo { return true, nil
if err := os.Chdir(repoDir); err == nil {
return true, nil
}
}
} }
fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories) fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories)
@ -262,7 +270,7 @@ func (app *App) setupRepo(
os.Exit(0) os.Exit(0)
} }
if didOpenRepo := openRecentRepo(app); didOpenRepo { if openRecentRepo(app) {
return true, nil return true, nil
} }

View file

@ -263,12 +263,14 @@ func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
} }
type MoveTodosUpInstruction struct { type MoveTodosUpInstruction struct {
Hashes []string Hashes []string
Distance int
} }
func NewMoveTodosUpInstruction(hashes []string) Instruction { func NewMoveTodosUpInstruction(hashes []string, distance int) Instruction {
return &MoveTodosUpInstruction{ return &MoveTodosUpInstruction{
Hashes: hashes, Hashes: hashes,
Distance: distance,
} }
} }
@ -288,17 +290,19 @@ func (self *MoveTodosUpInstruction) run(common *common.Common) error {
}) })
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodosUp(path, todosToMove, false, getCommentChar()) return utils.MoveTodos(path, todosToMove, false, -self.Distance, getCommentChar())
}) })
} }
type MoveTodosDownInstruction struct { type MoveTodosDownInstruction struct {
Hashes []string Hashes []string
Distance int
} }
func NewMoveTodosDownInstruction(hashes []string) Instruction { func NewMoveTodosDownInstruction(hashes []string, distance int) Instruction {
return &MoveTodosDownInstruction{ return &MoveTodosDownInstruction{
Hashes: hashes, Hashes: hashes,
Distance: distance,
} }
} }
@ -318,7 +322,7 @@ func (self *MoveTodosDownInstruction) run(common *common.Common) error {
}) })
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodosDown(path, todosToMove, false, getCommentChar()) return utils.MoveTodos(path, todosToMove, false, self.Distance, getCommentChar())
}) })
} }

View file

@ -102,7 +102,7 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
if cliArgs.PrintDefaultConfig { if cliArgs.PrintDefaultConfig {
var buf bytes.Buffer var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf) encoder := yaml.NewEncoder(&buf)
err := encoder.Encode(config.GetDefaultConfigForPlatform(runtime.GOOS)) err := encoder.Encode(config.GetDefaultConfigForPlatform(config.KeybindingPlatform()))
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }

View file

@ -16,7 +16,7 @@ type errorMapping struct {
func knownError(tr *i18n.TranslationSet, err error) (string, bool) { func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
errorMessage := err.Error() errorMessage := err.Error()
knownErrorMessages := []string{minGitVersionErrorMessage(tr)} knownErrorMessages := []string{minGitVersionErrorMessage(tr), tr.BareRepoNotSupported}
if lo.Contains(knownErrorMessages, errorMessage) { if lo.Contains(knownErrorMessages, errorMessage) {
return errorMessage, true return errorMessage, true

View file

@ -19,12 +19,12 @@ import (
"strings" "strings"
"github.com/jesseduffield/generics/maps" "github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/app" "github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
@ -49,7 +49,7 @@ func CommandToRun() string {
} }
func GetKeybindingsDir() string { func GetKeybindingsDir() string {
return utils.GetLazyRootDirectory() + "/docs-master/keybindings" return utils.MustFindLazygitRootDirectory() + "/docs-master/keybindings"
} }
func generateAtDir(cheatsheetDir string) { func generateAtDir(cheatsheetDir string) {
@ -196,7 +196,7 @@ func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string { func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {
var content strings.Builder var content strings.Builder
content.WriteString(fmt.Sprintf("# Lazygit %s\n", tr.Keybindings)) fmt.Fprintf(&content, "# Lazygit %s\n", tr.Keybindings)
for _, section := range bindingSections { for _, section := range bindingSections {
content.WriteString(formatTitle(section.title)) content.WriteString(formatTitle(section.title))

View file

@ -0,0 +1,130 @@
package direnv
import (
"bytes"
"encoding/json"
"os"
"os/exec"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
// LoadResult bundles everything callers might want to know about a direnv
// invocation. The env-var delta has already been applied to the process by
// the time Load returns.
type LoadResult struct {
// Message is whatever direnv printed to stderr — useful to log
// (success: "direnv: loading .envrc"; error: the error text).
Message string
// Err is non-nil when direnv exited non-zero or its stdout could
// not be parsed.
Err error
// Blocked is true when the target .envrc exists but hasn't been
// approved with `direnv allow` yet. EnvrcPath then holds the path
// direnv said was blocked, suitable for passing to Allow.
Blocked bool
EnvrcPath string
}
// Load runs `direnv export json` for the current working directory and applies
// the resulting env-var delta to the current process. If direnv isn't on PATH,
// it's a no-op — users who don't use direnv pay nothing, and users who do need
// no config to opt in.
func Load(cmd oscommands.ICmdObjBuilder) LoadResult {
if _, lookupErr := exec.LookPath("direnv"); lookupErr != nil {
return LoadResult{}
}
stdout, stderr, runErr := cmd.New([]string{
"direnv", "export", "json",
}).DontLog().RunWithOutputs()
result := LoadResult{Message: strings.TrimRight(stderr, "\n")}
// Apply whatever delta direnv produced even if it exited non-zero.
// When the new dir's .envrc is blocked, direnv still emits a valid
// JSON delta on stdout that unloads vars from the previous dir;
// without applying it the old env would leak into the new repo.
delta, parseErr := parseDirenvExport([]byte(stdout))
for k, v := range delta {
if v == nil {
_ = os.Unsetenv(k)
} else {
_ = os.Setenv(k, *v)
}
}
// Prefer the runtime error (whose Error() text is direnv's stderr)
// over a parse error, since it's the more actionable signal.
if runErr != nil {
result.Err = runErr
if envrcPath := queryBlockedEnvrc(cmd); envrcPath != "" {
result.Blocked = true
result.EnvrcPath = envrcPath
}
} else {
result.Err = parseErr
}
return result
}
// Allow runs `direnv allow <envrcPath>` to approve a .envrc file so the next
// Load can read it.
func Allow(cmd oscommands.ICmdObjBuilder, envrcPath string) error {
return cmd.New([]string{"direnv", "allow", envrcPath}).DontLog().Run()
}
func parseDirenvExport(stdout []byte) (map[string]*string, error) {
trimmed := bytes.TrimSpace(stdout)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
return nil, nil
}
var delta map[string]*string
if err := json.Unmarshal(trimmed, &delta); err != nil {
return nil, err
}
return delta, nil
}
// queryBlockedEnvrc asks direnv (via `status --json`) whether the current
// directory has a found-but-not-yet-allowed .envrc, and returns its path
// if so. We use direnv's structured output rather than parsing the
// human-readable "is blocked" line because the status output is more
// stable across versions and locales.
func queryBlockedEnvrc(cmd oscommands.ICmdObjBuilder) string {
stdout, _, err := cmd.New([]string{
"direnv", "status", "--json",
}).DontLog().RunWithOutputs()
if err != nil {
return ""
}
return parseDirenvStatus([]byte(stdout))
}
func parseDirenvStatus(stdout []byte) string {
var status struct {
State struct {
FoundRC *struct {
Allowed int `json:"allowed"`
Path string `json:"path"`
} `json:"foundRC"`
} `json:"state"`
}
if err := json.Unmarshal(stdout, &status); err != nil {
return ""
}
if status.State.FoundRC == nil {
return ""
}
// direnv's AllowStatus enum (`internal/cmd/rc.go`): 0=Allowed,
// 1=NotAllowed, 2=Denied. Only NotAllowed is something the user
// can approve; Denied means they already said no.
const notAllowed = 1
if status.State.FoundRC.Allowed != notAllowed {
return ""
}
return status.State.FoundRC.Path
}

View file

@ -0,0 +1,88 @@
package direnv
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseDirenvExport(t *testing.T) {
hello := "hello"
empty := ""
scenarios := []struct {
name string
input string
want map[string]*string
wantErr bool
}{
{name: "empty stdout means no .envrc was loaded", input: "", want: nil},
{name: "literal null from direnv means no delta", input: "null", want: nil},
{name: "empty object means no delta", input: "{}", want: map[string]*string{}},
{name: "string value is a set", input: `{"FOO":"hello"}`, want: map[string]*string{"FOO": &hello}},
{name: "null value is an unset", input: `{"FOO":null}`, want: map[string]*string{"FOO": nil}},
{
name: "set and unset can coexist",
input: `{"FOO":"hello","BAR":null,"BAZ":""}`,
want: map[string]*string{"FOO": &hello, "BAR": nil, "BAZ": &empty},
},
{name: "malformed JSON is an error", input: `{not json`, wantErr: true},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
got, err := parseDirenvExport([]byte(s.input))
if s.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, s.want, got)
}
})
}
}
func TestParseDirenvStatus(t *testing.T) {
scenarios := []struct {
name string
input string
want string
}{
{
name: "no .envrc found",
input: `{"state":{"foundRC":null}}`,
want: "",
},
{
name: "found and allowed (0)",
input: `{"state":{"foundRC":{"allowed":0,"path":"/repo/.envrc"}}}`,
want: "",
},
{
name: "found but not allowed (1) — eligible for approval",
input: `{"state":{"foundRC":{"allowed":1,"path":"/repo/.envrc"}}}`,
want: "/repo/.envrc",
},
{
name: "found but denied (2) — user already said no",
input: `{"state":{"foundRC":{"allowed":2,"path":"/repo/.envrc"}}}`,
want: "",
},
{
name: "malformed JSON",
input: `{not json`,
want: "",
},
{
name: "empty input",
input: "",
want: "",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
assert.Equal(t, s.want, parseDirenvStatus([]byte(s.input)))
})
}
}

View file

@ -11,6 +11,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -60,25 +61,42 @@ func NewGitCommand(
version *git_commands.GitVersion, version *git_commands.GitVersion,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
pagerConfig *config.PagerConfig, diffRendererConfigManager *config.DiffRendererConfigManager,
) (*GitCommand, error) { ) (*GitCommand, error) {
repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version) repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version)
if err != nil { if err != nil {
return nil, errors.Errorf("Error getting repo paths: %v", err) return nil, errors.Errorf("Error getting repo paths: %v", err)
} }
// A bare repo has no worktree for us to work in. Callers that can offer the
// user something better (app.setupRepo) check for this first; getting here
// means nobody could, e.g. because --git-dir was pointed at a bare repo.
if repoPaths.IsBareRepo() {
return nil, errors.New(cmn.Tr.BareRepoNotSupported)
}
err = os.Chdir(repoPaths.WorktreePath()) err = os.Chdir(repoPaths.WorktreePath())
if err != nil { if err != nil {
return nil, utils.WrapError(err) return nil, utils.WrapError(err)
} }
// Everything we run through the command builder gets told where the repo is
// by the builder itself, but subprocesses don't go through it: user-defined
// custom commands, an editor, and the lazygit we re-enter as git's sequence
// editor during a rebase. Put it in the process env for those.
env.SetGitLocationEnvVars(repoPaths.GitLocationEnvVars())
// Pin the config reads to the repo directory like all other git commands
// (see NewGitCmdObjBuilder); the config commands run outside that builder.
gitConfig.SetDir(repoPaths.WorktreePath())
return NewGitCommandAux( return NewGitCommandAux(
cmn, cmn,
version, version,
osCommand, osCommand,
gitConfig, gitConfig,
repoPaths, repoPaths,
pagerConfig, diffRendererConfigManager,
), nil ), nil
} }
@ -88,9 +106,9 @@ func NewGitCommandAux(
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths, repoPaths *git_commands.RepoPaths,
pagerConfig *config.PagerConfig, diffRendererConfigManager *config.DiffRendererConfigManager,
) *GitCommand { ) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd, repoPaths.WorktreePath(), repoPaths.GitLocationEnvVars())
// here we're doing a bunch of dependency injection for each of our commands structs. // here we're doing a bunch of dependency injection for each of our commands structs.
// This is admittedly messy, but allows us to test each command struct in isolation, // This is admittedly messy, but allows us to test each command struct in isolation,
@ -99,7 +117,7 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands // common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig) configCommands := git_commands.NewConfigCommands(cmn, gitConfig)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, configCommands, pagerConfig) gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, configCommands, diffRendererConfigManager)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands) fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon) statusCommands := git_commands.NewStatusCommands(gitCommon)
@ -117,8 +135,8 @@ func NewGitCommandAux(
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands) rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands) stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
patchBuilder := patch.NewPatchBuilder(cmn.Log, patchBuilder := patch.NewPatchBuilder(cmn.Log,
func(from string, to string, reverse bool, filename string, plain bool) (string, error) { func(from string, to string, reverse bool, filename string, previousPath string, plain bool) (string, error) {
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain) return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, previousPath, plain)
}) })
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder) patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon) bisectCommands := git_commands.NewBisectCommands(gitCommon)

View file

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -10,32 +11,55 @@ import (
type gitCmdObjBuilder struct { type gitCmdObjBuilder struct {
innerBuilder *oscommands.CmdObjBuilder innerBuilder *oscommands.CmdObjBuilder
// The directory of the repo (or worktree) this builder was created for;
// every command we produce runs there, regardless of the process's current
// working directory. The two are the same until the user switches to
// another repo: lazygit chdirs on a switch, but work still in flight for
// the previous repo (e.g. a background refresh spawning commands through
// the old builder) must keep running its commands against the repo it
// started in, not whichever one the process has since moved to.
repoDir string
// The env vars every command we produce gets: the optional-locks one below,
// plus the repo's git location if it has one (see
// RepoPaths.GitLocationEnvVars). Those are in the process env too, but for
// the same reason as repoDir we don't rely on that: the process env belongs
// to whichever repo lazygit has since switched to.
envVars []string
} }
var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{} var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{}
func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder) *gitCmdObjBuilder { // We disable git's optional locks on every command by default so that our git
// invocations never contend for index.lock. See git_commands.OptionalLocksEnvVar
// for the full rationale. Individual commands that do want the lock (currently
// only the foreground files refresh) opt back in via CmdObj.RemoveEnvVar.
var defaultEnvVar = git_commands.OptionalLocksEnvVar + "=0"
func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder, repoDir string, gitLocationEnvVars []string) *gitCmdObjBuilder {
// the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase) // the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase)
updatedBuilder := innerBuilder.CloneWithNewRunner(func(runner oscommands.ICmdObjRunner) oscommands.ICmdObjRunner { updatedBuilder := innerBuilder.CloneWithNewRunner(func(runner oscommands.ICmdObjRunner) oscommands.ICmdObjRunner {
return &gitCmdObjRunner{ return &gitCmdObjRunner{
log: log, log: log,
innerRunner: runner, innerRunner: runner,
initialRetryDelay: defaultInitialRetryDelay,
} }
}) })
return &gitCmdObjBuilder{ return &gitCmdObjBuilder{
innerBuilder: updatedBuilder, innerBuilder: updatedBuilder,
repoDir: repoDir,
envVars: append([]string{defaultEnvVar}, gitLocationEnvVars...),
} }
} }
var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0"
func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj { func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj {
return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar) return self.innerBuilder.New(args).AddEnvVars(self.envVars...).SetWd(self.repoDir)
} }
func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj { func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj {
return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar) return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(self.envVars...).SetWd(self.repoDir)
} }
func (self *gitCmdObjBuilder) Quote(str string) string { func (self *gitCmdObjBuilder) Quote(str string) string {

View file

@ -0,0 +1,61 @@
package commands
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
)
// Every git command we build disables optional locks by default, so that our
// invocations never contend for index.lock (see git_commands.OptionalLocksEnvVar
// for the rationale). Commands that want the lock opt back in with
// CmdObj.RemoveEnvVar.
func TestGitCmdObjBuilderDisablesOptionalLocksByDefault(t *testing.T) {
builder := NewGitCmdObjBuilder(
utils.NewDummyLog(),
oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
"/path/to/repo",
nil,
)
assert.Contains(t, builder.New([]string{"git", "status"}).GetEnvVars(), git_commands.OptionalLocksEnvVar+"=0")
assert.Contains(t, builder.NewShell("git status", "").GetEnvVars(), git_commands.OptionalLocksEnvVar+"=0")
}
// Every command the builder produces runs in the directory of the repo the
// builder was created for, not in the process's current directory: lazygit
// chdirs when switching repos, and commands built for the previous repo after
// that (e.g. by a background refresh still in flight) must keep addressing the
// repo they were built for.
func TestGitCmdObjBuilderPinsCommandsToRepoDir(t *testing.T) {
builder := NewGitCmdObjBuilder(
utils.NewDummyLog(),
oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
"/path/to/repo",
nil,
)
assert.Equal(t, "/path/to/repo", builder.New([]string{"git", "status"}).GetCmd().Dir)
assert.Equal(t, "/path/to/repo", builder.NewShell("git status", "").GetCmd().Dir)
}
// A repo whose git dir isn't in its worktree can't be found by running a
// command there, so the builder has to tell every command where it is; see
// RepoPaths.GitLocationEnvVars. The process env says the same thing, but only
// for the repo lazygit is in right now, which isn't necessarily this one.
func TestGitCmdObjBuilderPinsCommandsToGitLocation(t *testing.T) {
builder := NewGitCmdObjBuilder(
utils.NewDummyLog(),
oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
"/path/to/worktree",
[]string{"GIT_DIR=/path/to/repo/.git", "GIT_WORK_TREE=/path/to/worktree"},
)
assert.Subset(t, builder.New([]string{"git", "status"}).GetEnvVars(),
[]string{"GIT_DIR=/path/to/repo/.git", "GIT_WORK_TREE=/path/to/worktree"})
assert.Subset(t, builder.NewShell("git status", "").GetEnvVars(),
[]string{"GIT_DIR=/path/to/repo/.git", "GIT_WORK_TREE=/path/to/worktree"})
}

View file

@ -11,20 +11,42 @@ import (
// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock // here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock
const ( const (
WaitTime = 50 * time.Millisecond // defaultInitialRetryDelay is how long we wait before the first retry of a
RetryCount = 5 // command that failed with a transient lock error. We double it before each
// subsequent retry (see retryOnLockError), so across maxRetries attempts we
// wait for a bit over a second in total. That's long enough to outlast the
// brief window during which another git process holds a lock we need —
// typically our own foreground `git status` refresh, which takes index.lock
// to persist its refreshed stat-cache.
defaultInitialRetryDelay = 20 * time.Millisecond
maxRetries = 7
) )
type gitCmdObjRunner struct { type gitCmdObjRunner struct {
log *logrus.Entry log *logrus.Entry
innerRunner oscommands.ICmdObjRunner innerRunner oscommands.ICmdObjRunner
// initialRetryDelay is the wait before the first lock-error retry. It's a
// field rather than the constant directly so tests can set it to zero and
// not actually sleep.
initialRetryDelay time.Duration
} }
// isRetryableError returns true if the error output indicates a transient // isRetryableError returns true if a failed command hit a transient
// lock-related error that may succeed on retry // lock-related condition that may succeed on retry. The lock message can reach
func isRetryableError(output string) bool { // us either in the command's captured output or, for streamed commands whose
return strings.Contains(output, ".git/index.lock") || // output we don't capture, only in the returned error, so we check both.
strings.Contains(output, "cannot lock ref") //
// We match the bare "index.lock" fragment rather than a fuller path or message
// so we catch the lock wherever git puts it: the main .git dir, a linked
// worktree's git dir (.git/worktrees/<name>/index.lock), or a submodule's git
// dir.
func isRetryableError(output string, err error) bool {
text := output
if err != nil {
text += "\n" + err.Error()
}
return strings.Contains(text, "index.lock") ||
strings.Contains(text, "cannot lock ref")
} }
func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error { func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error {
@ -33,41 +55,44 @@ func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error {
} }
func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, error) { func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, error) {
var output string return self.retryOnLockError(func() (string, error) {
var err error return self.innerRunner.RunWithOutput(cmdObj.Clone())
for range RetryCount { })
newCmdObj := cmdObj.Clone()
output, err = self.innerRunner.RunWithOutput(newCmdObj)
if err == nil || !isRetryableError(output) {
return output, err
}
// if we have an error based on a lock, we should wait a bit and then retry
self.log.Warn("lock error prevented command from running. Retrying command after a small wait")
time.Sleep(WaitTime)
}
return output, err
} }
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj *oscommands.CmdObj) (string, string, error) { func (self *gitCmdObjRunner) RunWithOutputs(cmdObj *oscommands.CmdObj) (string, string, error) {
var stdout, stderr string var stdout, stderr string
var err error _, err := self.retryOnLockError(func() (string, error) {
for range RetryCount { var runErr error
newCmdObj := cmdObj.Clone() stdout, stderr, runErr = self.innerRunner.RunWithOutputs(cmdObj.Clone())
stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj) return stdout + stderr, runErr
})
return stdout, stderr, err
}
if err == nil || !isRetryableError(stdout+stderr) { // retryOnLockError runs the given function, retrying if it fails with a
return stdout, stderr, err // transient lock error (see isRetryableError). The string returned by run is
// the command output we inspect to classify the failure. We clone the command
// for each attempt (inside run) because an *exec.Cmd can only be run once.
func (self *gitCmdObjRunner) retryOnLockError(run func() (string, error)) (string, error) {
delay := self.initialRetryDelay
var output string
var err error
for attempt := range maxRetries {
output, err = run()
if err == nil || !isRetryableError(output, err) {
break
} }
// if we have an error based on a lock, we should wait a bit and then retry if attempt < maxRetries-1 {
self.log.Warn("lock error prevented command from running. Retrying command after a small wait") self.log.Warnf("lock error prevented command from running; retrying in %s", delay)
time.Sleep(WaitTime) time.Sleep(delay)
delay *= 2
}
} }
return stdout, stderr, err return output, err
} }
// Retry logic not implemented here, but these commands typically don't need to obtain a lock. // Retry logic not implemented here, but these commands typically don't need to obtain a lock.

View file

@ -0,0 +1,137 @@
package commands
import (
"errors"
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
)
type runnerResult struct {
output string
err error
}
// scriptedRunner is an ICmdObjRunner stub that returns a preconfigured result
// for each successive call, letting us drive the retry loop deterministically.
// It counts calls so tests can assert whether a command was retried.
type scriptedRunner struct {
results []runnerResult
calls int
}
func (self *scriptedRunner) next() (string, error) {
result := self.results[self.calls]
self.calls++
return result.output, result.err
}
func (self *scriptedRunner) Run(*oscommands.CmdObj) error {
_, err := self.next()
return err
}
func (self *scriptedRunner) RunWithOutput(*oscommands.CmdObj) (string, error) {
return self.next()
}
func (self *scriptedRunner) RunWithOutputs(*oscommands.CmdObj) (string, string, error) {
output, err := self.next()
return output, "", err
}
func (self *scriptedRunner) RunAndProcessLines(*oscommands.CmdObj, func(string) (bool, error)) error {
panic("not implemented")
}
func newTestRunner(inner *scriptedRunner) *gitCmdObjRunner {
return &gitCmdObjRunner{
log: utils.NewDummyLog(),
innerRunner: inner,
// don't actually sleep between retries
initialRetryDelay: 0,
}
}
// dummyCmdObj returns a throwaway command; only its clonability matters, since
// the scriptedRunner ignores it and returns preconfigured results.
func dummyCmdObj() *oscommands.CmdObj {
return oscommands.NewDummyCmdObjBuilder(nil).New([]string{"git", "status"})
}
func TestRunWithOutputReturnsSuccessWithoutRetrying(t *testing.T) {
inner := &scriptedRunner{results: []runnerResult{{output: "done", err: nil}}}
output, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.NoError(t, err)
assert.Equal(t, "done", output)
assert.Equal(t, 1, inner.calls)
}
func TestRunWithOutputDoesNotRetryNonLockError(t *testing.T) {
inner := &scriptedRunner{results: []runnerResult{{output: "boom", err: errors.New("boom")}}}
_, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.Error(t, err)
assert.Equal(t, 1, inner.calls)
}
func TestRunWithOutputRetriesWhenLockErrorIsInOutput(t *testing.T) {
inner := &scriptedRunner{results: []runnerResult{
{output: "fatal: Unable to create '/repo/.git/index.lock': File exists.", err: errors.New("exit status 128")},
{output: "done", err: nil},
}}
output, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.NoError(t, err)
assert.Equal(t, "done", output)
assert.Equal(t, 2, inner.calls)
}
func TestRunWithOutputRetriesWhenLockErrorIsOnlyInError(t *testing.T) {
// A streamed command (e.g. an amend run through the gpg helper) doesn't
// capture its output, so a lock failure surfaces only in the returned error
// with an empty output string. The retry logic must still recognize it.
inner := &scriptedRunner{results: []runnerResult{
{output: "", err: errors.New("fatal: Unable to create '/repo/.git/index.lock': File exists.")},
{output: "", err: nil},
}}
_, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.NoError(t, err)
assert.Equal(t, 2, inner.calls)
}
func TestRunWithOutputGivesUpAfterMaxRetries(t *testing.T) {
results := make([]runnerResult, maxRetries)
for i := range results {
results[i] = runnerResult{err: errors.New("fatal: Unable to create '/repo/.git/index.lock': File exists.")}
}
inner := &scriptedRunner{results: results}
_, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.Error(t, err)
assert.Equal(t, maxRetries, inner.calls)
}
func TestRunWithOutputRetriesLockErrorInLinkedWorktree(t *testing.T) {
// In a linked worktree the lock lives at .git/worktrees/<name>/index.lock
// rather than .git/index.lock, so only matching the bare "index.lock"
// fragment lets the retry fire there too.
inner := &scriptedRunner{results: []runnerResult{
{output: "", err: errors.New("fatal: Unable to create '/repo/.git/worktrees/feature/index.lock': File exists.")},
{output: "", err: nil},
}}
_, err := newTestRunner(inner).RunWithOutput(dummyCmdObj())
assert.NoError(t, err)
assert.Equal(t, 2, inner.calls)
}

View file

@ -29,5 +29,5 @@ func (self *BlameCommands) BlameLineRange(filename string, commit string, firstL
Arg("--"). Arg("--").
Arg(filename) Arg(filename)
return self.cmd.New(cmdArgs.ToArgv()).RunWithOutput() return self.cmd.New(cmdArgs.ToArgv()).DontLog().RunWithOutput()
} }

View file

@ -241,23 +241,15 @@ func (self *CommitCommands) AmendHeadCmdObj() *oscommands.CmdObj {
} }
func (self *CommitCommands) ShowCmdObj(hash string, filterPaths []string) *oscommands.CmdObj { func (self *CommitCommands) ShowCmdObj(hash string, filterPaths []string) *oscommands.CmdObj {
contextSize := self.UserConfig().Git.DiffContextSize
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false"). Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd). AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), true).
ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg("--color="+self.pagerConfig.GetColorArg()). Arg("--color=" + self.diffRendererConfigManager.GetColorArg()).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat"). Arg("--stat").
Arg("--decorate"). Arg("--decorate").
Arg("-p"). Arg("-p").
Arg(hash). Arg(hash).
ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
Arg("--"). Arg("--").
Arg(filterPaths...). Arg(filterPaths...).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).

View file

@ -1,12 +1,12 @@
package git_commands package git_commands
import ( import (
"fmt"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/samber/lo"
) )
type CommitFileLoader struct { type CommitFileLoader struct {
@ -29,7 +29,7 @@ func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse boo
Arg("--no-ext-diff"). Arg("--no-ext-diff").
Arg("--name-status"). Arg("--name-status").
Arg("-z"). Arg("-z").
Arg("--no-renames"). Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
ArgIf(reverse, "-R"). ArgIf(reverse, "-R").
Arg(from). Arg(from).
Arg(to). Arg(to).
@ -44,18 +44,37 @@ func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse boo
} }
// filenames string is something like "MM\x00file1\x00MU\x00file2\x00AA\x00file3\x00" // filenames string is something like "MM\x00file1\x00MU\x00file2\x00AA\x00file3\x00"
// so we need to split it by the null character and then map each status-name pair to a commit file // so we need to split it by the null character and then map each status-name pair
// to a commit file. Renames (and copies) are special: their status is followed by
// two paths (the old one and the new one) rather than one, e.g.
// "R100\x00old\x00new\x00".
func getCommitFilesFromFilenames(filenames string) []*models.CommitFile { func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00") fields := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00")
if len(lines) == 1 { if len(fields) == 1 {
return []*models.CommitFile{} return []*models.CommitFile{}
} }
// typical result looks like 'A my_file' meaning my_file was added commitFiles := make([]*models.CommitFile, 0, len(fields)/2)
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile { for i := 0; i < len(fields)-1; {
return &models.CommitFile{ changeStatus := fields[i]
ChangeStatus: chunk[0], if changeStatus[0] == 'R' || changeStatus[0] == 'C' {
Path: chunk[1], // The status has a similarity score appended (e.g. "R100"); drop it
// so the rest of the code only has to deal with a plain "R" or "C".
commitFiles = append(commitFiles, &models.CommitFile{
ChangeStatus: changeStatus[:1],
PreviousPath: fields[i+1],
Path: fields[i+2],
})
i += 3
} else {
// typical result looks like 'A my_file' meaning my_file was added
commitFiles = append(commitFiles, &models.CommitFile{
ChangeStatus: changeStatus,
Path: fields[i+1],
})
i += 2
} }
}) }
return commitFiles
} }

View file

@ -60,6 +60,25 @@ func TestGetCommitFilesFromFilenames(t *testing.T) {
}, },
}, },
}, },
{
testName: "a rename among regular files",
input: "M\x00Myfile\x00R100\x00before\x00after\x00A\x00Added\x00",
output: []*models.CommitFile{
{
Path: "Myfile",
ChangeStatus: "M",
},
{
Path: "after",
PreviousPath: "before",
ChangeStatus: "R",
},
{
Path: "Added",
ChangeStatus: "A",
},
},
},
} }
for _, test := range tests { for _, test := range tests {

View file

@ -174,7 +174,7 @@ func (self *CommitLoader) MergeRebasingCommits(hashPool *utils.StringPool, commi
} }
if workingTreeState.Rebasing { if workingTreeState.Rebasing {
rebasingCommits, err := self.getHydratedRebasingCommits(hashPool, addConflictedRebasingCommit) rebasingCommits, err := self.getHydratedRebasingCommits(hashPool, commits, addConflictedRebasingCommit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -251,8 +251,8 @@ func (self *CommitLoader) extractCommitFromLine(hashPool *utils.StringPool, line
}) })
} }
func (self *CommitLoader) getHydratedRebasingCommits(hashPool *utils.StringPool, addConflictingCommit bool) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedRebasingCommits(hashPool *utils.StringPool, existingCommits []*models.Commit, addConflictingCommit bool) ([]*models.Commit, error) {
return self.getHydratedTodoCommits(hashPool, self.getRebasingCommits(hashPool, addConflictingCommit), false) return self.getHydratedTodoCommits(hashPool, self.getRebasingCommits(hashPool, addConflictingCommit), existingCommits, false)
} }
func (self *CommitLoader) getHydratedSequencerCommits(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedSequencerCommits(hashPool *utils.StringPool, workingTreeState models.WorkingTreeState) ([]*models.Commit, error) {
@ -271,39 +271,56 @@ func (self *CommitLoader) getHydratedSequencerCommits(hashPool *utils.StringPool
} }
} }
return self.getHydratedTodoCommits(hashPool, commits, true) return self.getHydratedTodoCommits(hashPool, commits, nil, true)
} }
func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, todoCommits []*models.Commit, todoFileHasShortHashes bool) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedTodoCommits(
hashPool *utils.StringPool,
todoCommits []*models.Commit,
existingCommits []*models.Commit,
todoFileHasShortHashes bool,
) ([]*models.Commit, error) {
if len(todoCommits) == 0 { if len(todoCommits) == 0 {
return nil, nil return nil, nil
} }
commitHashes := lo.FilterMap(todoCommits, func(commit *models.Commit, _ int) (string, bool) { // A refresh of only the rebasing todos should reuse the already loaded todos to avoid
return commit.Hash(), commit.Hash() != "" // unnecessary git show calls.
})
// note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage
cmdObj := self.cmd.New(
NewGitCmd("show").
Config("log.showSignature=false").
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
Arg(commitHashes...).
ToArgv(),
).DontLog()
fullCommits := map[string]*models.Commit{} fullCommits := map[string]*models.Commit{}
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { for _, commit := range existingCommits {
if line == "" || line[0] != '+' { if commit.IsTODO() && commit.Hash() != "" {
return false, nil // Make a copy of the commit; that's necessary to avoid mutating the original commit
// when we later reuse it in the loop at the end of this function.
fullCommits[commit.Hash()] = lo.ToPtr(*commit)
} }
commit := self.extractCommitFromLine(hashPool, line[1:], false) }
fullCommits[commit.Hash()] = commit
return false, nil commitHashesToFetch := lo.FilterMap(todoCommits, func(commit *models.Commit, _ int) (string, bool) {
return commit.Hash(), commit.Hash() != "" && fullCommits[commit.Hash()] == nil
}) })
if err != nil {
return nil, err if len(commitHashesToFetch) > 0 {
// note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage
cmdObj := self.cmd.New(
NewGitCmd("show").
Config("log.showSignature=false").
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
Arg(commitHashesToFetch...).
ToArgv(),
).DontLog()
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
if line == "" || line[0] != '+' {
return false, nil
}
commit := self.extractCommitFromLine(hashPool, line[1:], false)
fullCommits[commit.Hash()] = commit
return false, nil
})
if err != nil {
return nil, err
}
} }
findFullCommit := lo.Ternary(todoFileHasShortHashes, findFullCommit := lo.Ternary(todoFileHasShortHashes,

View file

@ -538,6 +538,110 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
} }
} }
func TestCommitLoaderGetHydratedTodoCommitsReusesExistingCommit(t *testing.T) {
hashPool := &utils.StringPool{}
runner := oscommands.NewFakeRunner(t)
loader := &CommitLoader{
cmd: oscommands.NewDummyCmdObjBuilder(runner),
}
existingCommit := models.NewCommit(hashPool, models.NewCommitOpts{
Hash: "0123456789012345678901234567890123456789",
Name: "hydrated subject",
AuthorName: "Jane Doe",
AuthorEmail: "jane@example.com",
UnixTimestamp: 1234,
Parents: []string{"1123456789012345678901234567890123456789"},
Status: models.StatusRebasing,
Action: todo.Pick,
})
refreshedTodo := models.NewCommit(hashPool, models.NewCommitOpts{
Hash: existingCommit.Hash(),
Name: "subject from the todo file",
Status: models.StatusConflicted,
Action: todo.Fixup,
ActionFlag: "-C",
})
commits, err := loader.getHydratedTodoCommits(
hashPool,
[]*models.Commit{refreshedTodo},
[]*models.Commit{existingCommit},
false,
)
assert.NoError(t, err)
assert.Equal(t, []*models.Commit{
models.NewCommit(hashPool, models.NewCommitOpts{
Hash: existingCommit.Hash(),
Name: "hydrated subject",
AuthorName: "Jane Doe",
AuthorEmail: "jane@example.com",
UnixTimestamp: 1234,
Parents: []string{"1123456789012345678901234567890123456789"},
Status: models.StatusConflicted,
Action: todo.Fixup,
ActionFlag: "-C",
}),
}, commits)
assert.Equal(t, todo.Pick, existingCommit.Action)
assert.Equal(t, models.StatusRebasing, existingCommit.Status)
runner.CheckForMissingCalls()
}
func TestCommitLoaderGetHydratedTodoCommitsLoadsMissingCommit(t *testing.T) {
hashPool := &utils.StringPool{}
existingHash := "0123456789012345678901234567890123456789"
missingHash := "2123456789012345678901234567890123456789"
missingCommitOutput := strings.ReplaceAll(
`+2123456789012345678901234567890123456789|1235|John Doe|john@example.com||>|tag: new|new subject`,
"|",
"\x00",
)
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(
[]string{
"-c", "log.showSignature=false", "show", "--no-patch", "--oneline", "--abbrev=20",
prettyFormat, missingHash,
},
missingCommitOutput,
nil,
)
loader := &CommitLoader{
cmd: oscommands.NewDummyCmdObjBuilder(runner),
}
existingCommit := models.NewCommit(hashPool, models.NewCommitOpts{
Hash: existingHash,
Name: "existing subject",
Status: models.StatusRebasing,
Action: todo.Pick,
})
refreshedTodos := []*models.Commit{
models.NewCommit(hashPool, models.NewCommitOpts{
Hash: existingHash,
Status: models.StatusRebasing,
Action: todo.Pick,
}),
models.NewCommit(hashPool, models.NewCommitOpts{
Hash: missingHash,
Status: models.StatusRebasing,
Action: todo.Edit,
}),
}
commits, err := loader.getHydratedTodoCommits(
hashPool,
refreshedTodos,
[]*models.Commit{existingCommit},
false,
)
assert.NoError(t, err)
assert.Len(t, commits, 2)
assert.Equal(t, "existing subject", commits[0].Name)
assert.Equal(t, "new subject", commits[1].Name)
assert.Equal(t, todo.Edit, commits[1].Action)
runner.CheckForMissingCalls()
}
func TestCommitLoader_setCommitStatuses(t *testing.T) { func TestCommitLoader_setCommitStatuses(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string

View file

@ -255,7 +255,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize uint64 contextSize uint64
similarityThreshold int similarityThreshold int
ignoreWhitespace bool ignoreWhitespace bool
pagerConfig *config.PagingConfig diffRendererConfig *config.DiffRendererConfig
expected []string expected []string
} }
@ -266,8 +266,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: nil, diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
{ {
testName: "Default case with filter path", testName: "Default case with filter path",
@ -275,8 +275,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: nil, diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--", "file.txt"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@ -284,8 +284,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 77, contextSize: 77,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: nil, diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
{ {
testName: "Show diff with custom similarity threshold", testName: "Show diff with custom similarity threshold",
@ -293,8 +293,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 33, similarityThreshold: 33,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: nil, diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=33%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
{ {
testName: "Show diff, ignoring whitespace", testName: "Show diff, ignoring whitespace",
@ -302,8 +302,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 77, contextSize: 77,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: true, ignoreWhitespace: true,
pagerConfig: nil, diffRendererConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space", "--find-renames=50%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--unified=77", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
{ {
testName: "Show diff with external diff command", testName: "Show diff with external diff command",
@ -311,8 +311,8 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"}, diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
{ {
testName: "Show diff using git's external diff config", testName: "Show diff using git's external diff config",
@ -320,16 +320,16 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true}, diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"}, expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--stat", "--decorate", "-p", "1234567890", "--"},
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
if s.pagerConfig != nil { if s.diffRendererConfig != nil {
userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig} userConfig.Git.DiffRenderers = []config.DiffRendererConfig{*s.diffRendererConfig}
} }
userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
userConfig.Git.DiffContextSize = s.contextSize userConfig.Git.DiffContextSize = s.contextSize

View file

@ -8,12 +8,12 @@ import (
type GitCommon struct { type GitCommon struct {
*common.Common *common.Common
version *GitVersion version *GitVersion
cmd oscommands.ICmdObjBuilder cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand os *oscommands.OSCommand
repoPaths *RepoPaths repoPaths *RepoPaths
config *ConfigCommands config *ConfigCommands
pagerConfig *config.PagerConfig diffRendererConfigManager *config.DiffRendererConfigManager
} }
func NewGitCommon( func NewGitCommon(
@ -23,15 +23,15 @@ func NewGitCommon(
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
repoPaths *RepoPaths, repoPaths *RepoPaths,
config *ConfigCommands, config *ConfigCommands,
pagerConfig *config.PagerConfig, diffRendererConfigManager *config.DiffRendererConfigManager,
) *GitCommon { ) *GitCommon {
return &GitCommon{ return &GitCommon{
Common: cmn, Common: cmn,
version: version, version: version,
cmd: cmd, cmd: cmd,
os: osCommand, os: osCommand,
repoPaths: repoPaths, repoPaths: repoPaths,
config: config, config: config,
pagerConfig: pagerConfig, diffRendererConfigManager: diffRendererConfigManager,
} }
} }

View file

@ -62,7 +62,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common.SetUserConfig(config.GetDefaultConfig()) gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
} }
gitCommon.pagerConfig = config.NewPagerConfig(func() *config.UserConfig { gitCommon.diffRendererConfigManager = config.NewDiffRendererConfigManager(func() *config.UserConfig {
return gitCommon.Common.UserConfig() return gitCommon.Common.UserConfig()
}) })
@ -168,6 +168,12 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
return NewBranchCommands(gitCommon) return NewBranchCommands(gitCommon)
} }
func buildStatusCommands(deps commonDeps) *StatusCommands {
gitCommon := buildGitCommon(deps)
return NewStatusCommands(gitCommon)
}
func buildFlowCommands(deps commonDeps) *FlowCommands { func buildFlowCommands(deps commonDeps) *FlowCommands {
gitCommon := buildGitCommon(deps) gitCommon := buildGitCommon(deps)

View file

@ -17,22 +17,14 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
} }
// This is for generating diffs to be shown in the UI (e.g. rendering a range // This is for generating diffs to be shown in the UI (e.g. rendering a range
// diff to the main view). It uses a custom pager if one is configured. // diff to the main view). It uses a custom diff renderer if one is configured.
func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj { func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != ""
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
ignoreWhitespace := self.UserConfig().Git.IgnoreWhitespaceInDiffView
return self.cmd.New( return self.cmd.New(
NewGitCmd("diff"). NewGitCmd("diff").
Config("diff.noprefix=false"). Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd). AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), true).
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())). Arg(fmt.Sprintf("--color=%s", self.diffRendererConfigManager.GetColorArg())).
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
Arg(diffArgs...). Arg(diffArgs...).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).
ToArgv(), ToArgv(),
@ -40,8 +32,8 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
} }
// This is a basic generic diff command that can be used for any diff operation // This is a basic generic diff command that can be used for any diff operation
// (e.g. copying a diff to the clipboard). It will not use a custom pager, and // (e.g. copying a diff to the clipboard). It will not use a custom diff renderer,
// does not use user configs such as ignore whitespace. // and does not use user configs such as ignore whitespace.
// If you want to diff specific refs (one or two), you need to add them yourself // If you want to diff specific refs (one or two), you need to add them yourself
// in additionalArgs; it is recommended to also pass `--` after that. If you // in additionalArgs; it is recommended to also pass `--` after that. If you
// want to restrict the diff to specific paths, pass them in additionalArgs // want to restrict the diff to specific paths, pass them in additionalArgs

View file

@ -2,6 +2,7 @@ package git_commands
import ( import (
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -93,7 +94,7 @@ func (self *FileCommands) guessDefaultEditor() string {
// At this point, it might be more than just the name of the editor; // At this point, it might be more than just the name of the editor;
// e.g. it might be "code -w" or "vim -u myvim.rc". So assume that // e.g. it might be "code -w" or "vim -u myvim.rc". So assume that
// everything up to the first space is the editor name. // everything up to the first space is the editor name.
editor = strings.Split(editor, " ")[0] editor = filepath.Base(strings.Split(editor, " ")[0])
} }
return editor return editor

View file

@ -2,12 +2,12 @@ package git_commands
import ( import (
"fmt" "fmt"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
) )
type FileLoaderConfig interface { type FileLoaderConfig interface {
@ -36,6 +36,12 @@ type GetStatusFileOptions struct {
// This is useful for users with bare repos for dotfiles who default to hiding untracked files, // This is useful for users with bare repos for dotfiles who default to hiding untracked files,
// but want to occasionally see them to `git add` a new file. // but want to occasionally see them to `git add` a new file.
ForceShowUntracked bool ForceShowUntracked bool
// When true, this status is part of an unattended background refresh, so it
// keeps the default suppression of optional locks (avoiding index.lock
// contention with git commands the user runs in a terminal, at the cost of
// not persisting git's refreshed stat-cache). A foreground status opts back
// in; see gitStatus.
Background bool
} }
func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File { func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
@ -47,7 +53,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
} }
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg, Background: opts.Background})
if err != nil { if err != nil {
self.Log.Error(err) self.Log.Error(err)
} }
@ -82,27 +88,66 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
files = append(files, file) files = append(files, file)
} }
// Go through the files to see if any of these files are actually worktrees self.setConflictMarkerSizes(files)
// so that we can render them correctly
worktreePaths := linkedWortkreePaths(self.Fs, self.repoPaths.RepoGitDirPath()) return files
for _, file := range files { }
for _, worktreePath := range worktreePaths {
absFilePath, err := filepath.Abs(file.Path) // Looks up how long the conflict markers in the conflicted files are. We ask
if err != nil { // git for all of them at once, because spawning a process per file would be
self.Log.Error(err) // painfully slow when hundreds of files are conflicted (especially on Windows).
continue func (self *FileLoader) setConflictMarkerSizes(files []*models.File) {
} conflictedFiles := lo.Filter(files, func(file *models.File, _ int) bool {
if absFilePath == worktreePath { return file.HasInlineMergeConflicts
file.IsWorktree = true })
// `git status` renders this worktree as a folder with a trailing slash but we'll represent it as a singular worktree if len(conflictedFiles) == 0 {
// If we include the slash, it will be rendered as a folder with a null file inside. return
file.Path = strings.TrimSuffix(file.Path, "/") }
break
} paths := lo.Map(conflictedFiles, func(file *models.File, _ int) string {
return file.Path
})
markerSizes, err := self.getConflictMarkerSizes(paths)
if err != nil {
self.Log.Error(err)
return
}
for _, file := range conflictedFiles {
file.ConflictMarkerSize = markerSizes[file.Path]
}
}
func (self *FileLoader) getConflictMarkerSizes(paths []string) (map[string]int, error) {
cmdArgs := NewGitCmd("check-attr").
Arg("-z").
Arg("--stdin").
Arg("conflict-marker-size").
ToArgv()
// -z makes git both read the paths and write its output NUL-separated, so
// that paths containing newlines don't throw us off.
output, _, err := self.cmd.New(cmdArgs).
SetStdin(strings.Join(paths, "\x00")).
DontLog().
RunWithOutputs()
if err != nil {
return nil, err
}
markerSizes := map[string]int{}
fields := strings.Split(output, "\x00")
// Each path yields a path/attribute/value triple; the value is either a
// number or something like "unspecified", in which case we leave the marker
// size at 0 to say that git's default applies.
for i := 0; i+2 < len(fields); i += 3 {
if markerSize, err := strconv.Atoi(fields[i+2]); err == nil && markerSize > 0 {
markerSizes[fields[i]] = markerSize
} }
} }
return files return markerSizes, nil
} }
type FileDiff struct { type FileDiff struct {
@ -148,6 +193,7 @@ func (self *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
type GitStatusOptions struct { type GitStatusOptions struct {
NoRenames bool NoRenames bool
UntrackedFilesArg string UntrackedFilesArg string
Background bool
} }
type FileStatus struct { type FileStatus struct {
@ -179,7 +225,17 @@ func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
). ).
ToArgv() ToArgv()
statusLines, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs() cmdObj := self.cmd.New(cmdArgs).DontLog()
if !opts.Background {
// Every git command suppresses optional locks by default (see
// OptionalLocksEnvVar). A foreground refresh is the one exception: we let
// it take the lock so it persists git's refreshed stat-cache, which keeps
// subsequent status calls fast. Background refreshes leave it suppressed so
// they can't contend for index.lock.
cmdObj.RemoveEnvVar(OptionalLocksEnvVar)
}
statusLines, _, err := cmdObj.RunWithOutputs()
if err != nil { if err != nil {
return []FileStatus{}, err return []FileStatus{}, err
} }

View file

@ -37,6 +37,10 @@ func TestFileGetStatusFiles(t *testing.T) {
ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"}, ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"},
"4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt", "4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt",
nil, nil,
).
ExpectGitArgs([]string{"check-attr", "-z", "--stdin", "conflict-marker-size"},
"file5.txt\x00conflict-marker-size\x00unspecified\x00",
nil,
), ),
showNumstatInFilesView: true, showNumstatInFilesView: true,
expectedFiles: []*models.File{ expectedFiles: []*models.File{
@ -112,6 +116,58 @@ func TestFileGetStatusFiles(t *testing.T) {
}, },
}, },
}, },
{
testName: "Conflicted files with a conflict-marker-size attribute",
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
"UU file1.txt\x00UU file2.txt\x00UU file3.txt\x00 M file4.txt",
nil,
).
ExpectGitArgs([]string{"check-attr", "-z", "--stdin", "conflict-marker-size"},
"file1.txt\x00conflict-marker-size\x0032\x00"+
"file2.txt\x00conflict-marker-size\x00unspecified\x00"+
"file3.txt\x00conflict-marker-size\x00nonsense\x00",
nil,
),
expectedFiles: []*models.File{
{
Path: "file1.txt",
HasUnstagedChanges: true,
Tracked: true,
HasMergeConflicts: true,
HasInlineMergeConflicts: true,
ConflictMarkerSize: 32,
DisplayString: "UU file1.txt",
ShortStatus: "UU",
},
{
Path: "file2.txt",
HasUnstagedChanges: true,
Tracked: true,
HasMergeConflicts: true,
HasInlineMergeConflicts: true,
DisplayString: "UU file2.txt",
ShortStatus: "UU",
},
{
Path: "file3.txt",
HasUnstagedChanges: true,
Tracked: true,
HasMergeConflicts: true,
HasInlineMergeConflicts: true,
DisplayString: "UU file3.txt",
ShortStatus: "UU",
},
{
Path: "file4.txt",
HasUnstagedChanges: true,
Tracked: true,
DisplayString: " M file4.txt",
ShortStatus: " M",
},
},
},
{ {
testName: "File with new line char", testName: "File with new line char",
similarityThreshold: 50, similarityThreshold: 50,

View file

@ -203,6 +203,17 @@ func TestGuessDefaultEditor(t *testing.T) {
}, },
expectedResult: "bbedit", expectedResult: "bbedit",
}, },
{
gitConfigMockResponses: nil,
getenv: func(env string) string {
if env == "EDITOR" {
return "/usr/bin/nvim"
}
return ""
},
expectedResult: "nvim",
},
} }
for _, s := range scenarios { for _, s := range scenarios {

View file

@ -1,11 +1,33 @@
package git_commands package git_commands
import ( import (
"fmt"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
) )
// OptionalLocksEnvVar is the name of the environment variable that tells git
// whether it may take "optional" locks — chiefly the index.lock that `git
// status` grabs to write back a refreshed stat-cache. We set it to 0 on every
// git command by default (see NewGitCmdObjBuilder) so our invocations never
// contend for index.lock, neither with each other (e.g. a main-view `git diff
// --submodule`, which runs `git status` inside submodules, racing a submodule
// action) nor with git commands the user runs in a terminal. The one command
// that opts back in is the foreground files refresh; see FileLoader.gitStatus.
const OptionalLocksEnvVar = "GIT_OPTIONAL_LOCKS"
// forOtherRepo prepares a command that operates on a repo other than the one
// we have open — a submodule, or another worktree. GIT_DIR and GIT_WORK_TREE
// say where our repo is, and every command we run inherits them, so a command
// pointed at a different repo would be resolved against ours instead: `git -C
// <submodule> log` would silently log the superproject's commits.
func forOtherRepo(cmdObj *oscommands.CmdObj) *oscommands.CmdObj {
return cmdObj.RemoveEnvVar(env.GitDirEnvVar).RemoveEnvVar(env.GitWorkTreeEnvVar)
}
// convenience struct for building git commands. Especially useful when // convenience struct for building git commands. Especially useful when
// including conditional args // including conditional args
type GitCommandBuilder struct { type GitCommandBuilder struct {
@ -101,6 +123,20 @@ func (self *GitCommandBuilder) GitDirIf(condition bool, path string) *GitCommand
return self return self
} }
func (self *GitCommandBuilder) AddCommonDiffArgs(diffRendererConfigManager *config.DiffRendererConfigManager, userConfig *config.UserConfig, forUI bool) *GitCommandBuilder {
contextSize := userConfig.Git.DiffContextSize
extDiffCmd := diffRendererConfigManager.GetExternalDiffCommand(contextSize)
useExtDiff := forUI && diffRendererConfigManager.GetDiffRendererType() == config.DiffRendererType_ExtDiff
return self.
ConfigIf(forUI && extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
ArgIf(forUI && userConfig.Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", userConfig.Git.RenameSimilarityThreshold)).
ArgIf(forUI, diffRendererConfigManager.GetRawGitArgs()...)
}
func (self *GitCommandBuilder) ToArgv() []string { func (self *GitCommandBuilder) ToArgv() []string {
return append([]string{"git"}, self.args...) return append([]string{"git"}, self.args...)
} }

View file

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os"
"os/exec"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -85,12 +87,25 @@ type PullRequestNode struct {
HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"` HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"`
State string `json:"state"` State string `json:"state"`
IsDraft bool `json:"isDraft"` IsDraft bool `json:"isDraft"`
HeadRef GithubRef `json:"headRef"`
} }
type GithubRepositoryOwner struct { type GithubRepositoryOwner struct {
Login string `json:"login"` Login string `json:"login"`
} }
type GithubRef struct {
Target GithubGitObject `json:"target"`
}
type GithubGitObject struct {
StatusCheckRollup GithubStatusCheckRollup `json:"statusCheckRollup"`
}
type GithubStatusCheckRollup struct {
State string `json:"state"`
}
type graphQLRequest struct { type graphQLRequest struct {
Query string `json:"query"` Query string `json:"query"`
Variables map[string]string `json:"variables"` Variables map[string]string `json:"variables"`
@ -121,6 +136,15 @@ func fetchPullRequestsQuery(branches []string, owner string, repo string) (strin
number number
url url
isDraft isDraft
headRef {
target {
... on Commit {
statusCheckRollup {
state
}
}
}
}
headRepositoryOwner { headRepositoryOwner {
login login
} }
@ -138,9 +162,51 @@ func fetchPullRequestsQuery(branches []string, owner string, repo string) (strin
return queryString, variables return queryString, variables
} }
// GetAuthToken returns the token to authenticate against the given host with,
// or an empty string if there is none.
//
// The token has to come from gh itself rather than from an in-process lookup
// with go-gh: that reads gh's config file once per process and answers from
// that snapshot ever after, whereas gh rewrites the file whenever the active
// account changes, and keeps the active account's token either there or in the
// system keyring. Under a long-running lazygit the snapshot therefore drifts
// out of date, leaving us with a token for an account that is no longer active,
// or with no token at all.
func (self *GitHubCommands) GetAuthToken(host string) string { func (self *GitHubCommands) GetAuthToken(host string) string {
token, _ := auth.TokenForHost(host) ghExe := ghExecutable()
return token if ghExe == "" {
// Without gh installed, the environment variables and config file that
// gh would have consulted are still worth a look.
token, _ := auth.TokenFromEnvOrConfig(host)
return token
}
cmdArgs := []string{ghExe, "auth", "token", "--hostname", host}
output, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
if err != nil {
// Not being logged in to this host is a normal state rather than
// something to report; the runner logs gh's stderr for the rest.
return ""
}
return strings.TrimSpace(output)
}
// ghExecutable returns the path of the gh binary, or an empty string if it
// isn't installed.
func ghExecutable() string {
if ghExe := os.Getenv("GH_PATH"); ghExe != "" {
return ghExe
}
// A gh found in the current directory rather than on PATH comes back as
// exec.ErrDot, which we treat as not having found one at all.
ghExe, err := exec.LookPath("gh")
if err != nil {
return ""
}
return ghExe
} }
// FetchRecentPRs fetches recent pull requests using GraphQL. serviceInfo // FetchRecentPRs fetches recent pull requests using GraphQL. serviceInfo
@ -210,7 +276,10 @@ func (self *GitHubCommands) fetchRecentPRsAux(endpoint string, repoOwner string,
req.Header.Set("Authorization", "token "+token) req.Header.Set("Authorization", "token "+token)
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
client := &http.Client{} // Bound the request so that a dead or extremely slow network can't leave
// the pull-request refresh in flight for minutes. The data is auxiliary,
// so giving up and retrying on the next refresh beats waiting.
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -228,9 +297,12 @@ func (self *GitHubCommands) fetchRecentPRsAux(endpoint string, repoOwner string,
return nil, err return nil, err
} }
return parsePullRequestsResponse(respBytes)
}
func parsePullRequestsResponse(respBytes []byte) ([]*models.GithubPullRequest, error) {
var result Response var result Response
err = json.Unmarshal(respBytes, &result) if err := json.Unmarshal(respBytes, &result); err != nil {
if err != nil {
return nil, err return nil, err
} }
@ -243,6 +315,7 @@ func (self *GitHubCommands) fetchRecentPRsAux(endpoint string, repoOwner string,
Number: node.Number, Number: node.Number,
Title: node.Title, Title: node.Title,
State: lo.Ternary(node.IsDraft && node.State != "CLOSED", "DRAFT", node.State), State: lo.Ternary(node.IsDraft && node.State != "CLOSED", "DRAFT", node.State),
ChecksState: node.HeadRef.Target.StatusCheckRollup.State,
Url: node.Url, Url: node.Url,
HeadRepositoryOwner: models.GithubRepositoryOwner{ HeadRepositoryOwner: models.GithubRepositoryOwner{
Login: node.HeadRepositoryOwner.Login, Login: node.HeadRepositoryOwner.Login,

View file

@ -76,6 +76,104 @@ func TestGraphQLEndpoint(t *testing.T) {
} }
} }
func TestFetchPullRequestsQueryFetchesOnlyAggregateCheckState(t *testing.T) {
query, variables := fetchPullRequestsQuery([]string{"feature"}, "owner", "repo")
assert.Contains(t, query, "headRef {")
assert.Contains(t, query, "... on Commit {")
assert.Contains(t, query, "statusCheckRollup {")
assert.NotContains(t, query, "contexts")
assert.Equal(t, map[string]string{
"owner": "owner",
"repo": "repo",
"branch1": "feature",
}, variables)
}
func TestParsePullRequestsResponse(t *testing.T) {
t.Run("flattens aliases and normalizes drafts", func(t *testing.T) {
response := []byte(`{
"data": {
"repository": {
"a1": {
"edges": [
{
"node": {
"title": "Add feature",
"headRefName": "feature",
"number": 42,
"url": "https://github.com/jesseduffield/lazygit/pull/42",
"headRepositoryOwner": {"login": "contributor"},
"state": "OPEN",
"isDraft": false,
"headRef": {
"target": {
"statusCheckRollup": {"state": "SUCCESS"}
}
}
}
}
]
},
"a2": {
"edges": [
{
"node": {
"title": "Draft feature",
"headRefName": "draft-feature",
"number": 43,
"url": "https://github.com/jesseduffield/lazygit/pull/43",
"headRepositoryOwner": {"login": "contributor"},
"state": "OPEN",
"isDraft": true,
"headRef": null
}
}
]
}
}
}
}`)
prs, err := parsePullRequestsResponse(response)
assert.NoError(t, err)
assert.ElementsMatch(t, []*models.GithubPullRequest{
{
HeadRefName: "feature",
Number: 42,
Title: "Add feature",
State: "OPEN",
ChecksState: "SUCCESS",
Url: "https://github.com/jesseduffield/lazygit/pull/42",
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
},
{
HeadRefName: "draft-feature",
Number: 43,
Title: "Draft feature",
State: "DRAFT",
Url: "https://github.com/jesseduffield/lazygit/pull/43",
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
},
}, prs)
})
t.Run("returns an empty slice for an empty result", func(t *testing.T) {
prs, err := parsePullRequestsResponse([]byte(`{"data":{"repository":{}}}`))
assert.NoError(t, err)
assert.Empty(t, prs)
})
t.Run("rejects malformed JSON", func(t *testing.T) {
prs, err := parsePullRequestsResponse([]byte(`{"data":`))
assert.Error(t, err)
assert.Nil(t, prs)
})
}
func TestGenerateGithubPullRequestMap(t *testing.T) { func TestGenerateGithubPullRequestMap(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
@ -99,6 +197,7 @@ func TestGenerateGithubPullRequestMap(t *testing.T) {
Number: 42, Number: 42,
Title: "Add feature", Title: "Add feature",
State: "OPEN", State: "OPEN",
ChecksState: "PENDING",
Url: "https://github.com/jesseduffield/lazygit/pull/42", Url: "https://github.com/jesseduffield/lazygit/pull/42",
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"}, HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
}, },
@ -122,6 +221,7 @@ func TestGenerateGithubPullRequestMap(t *testing.T) {
Number: 42, Number: 42,
Title: "Add feature", Title: "Add feature",
State: "OPEN", State: "OPEN",
ChecksState: "PENDING",
Url: "https://github.com/jesseduffield/lazygit/pull/42", Url: "https://github.com/jesseduffield/lazygit/pull/42",
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"}, HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
}, },

View file

@ -112,29 +112,30 @@ func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end in
} }
func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error { func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error {
baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+2) return self.MoveCommits(commits, startIdx, endIdx, 1)
hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string {
return commit.Hash()
})
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseHashOrRoot,
instruction: daemon.NewMoveTodosDownInstruction(hashes),
overrideEditor: true,
}).Run()
} }
func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error { func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error {
baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+1) return self.MoveCommits(commits, startIdx, endIdx, -1)
}
func (self *RebaseCommands) MoveCommits(commits []*models.Commit, startIdx int, endIdx int, offset int) error {
baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+max(offset, 0)+1)
hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string { hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string {
return commit.Hash() return commit.Hash()
}) })
var instruction daemon.Instruction
if offset > 0 {
instruction = daemon.NewMoveTodosDownInstruction(hashes, offset)
} else {
instruction = daemon.NewMoveTodosUpInstruction(hashes, -offset)
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseHashOrRoot, baseHashOrRoot: baseHashOrRoot,
instruction: daemon.NewMoveTodosUpInstruction(hashes), instruction: instruction,
overrideEditor: true, overrideEditor: true,
}).Run() }).Run()
} }
@ -369,21 +370,20 @@ func (self *RebaseCommands) DeleteUpdateRefTodos(commits []*models.Commit) error
} }
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error { func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo") return self.MoveTodos(commits, 1)
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
return todoFromCommit(commit)
})
return utils.MoveTodosDown(fileName, todosToMove, true, self.config.GetCoreCommentChar())
} }
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error { func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
return self.MoveTodos(commits, -1)
}
func (self *RebaseCommands) MoveTodos(commits []*models.Commit, offset int) error {
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo") fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
return todoFromCommit(commit) return todoFromCommit(commit)
}) })
return utils.MoveTodosUp(fileName, todosToMove, true, self.config.GetCoreCommentChar()) return utils.MoveTodos(fileName, todosToMove, true, offset, self.config.GetCoreCommentChar())
} }
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one // SquashAllAboveFixupCommits squashes all fixup! commits above the given one

View file

@ -1,15 +1,14 @@
package git_commands package git_commands
import ( import (
ioFs "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spf13/afero"
) )
type RepoPaths struct { type RepoPaths struct {
@ -19,10 +18,12 @@ type RepoPaths struct {
repoGitDirPath string repoGitDirPath string
repoName string repoName string
isBareRepo bool isBareRepo bool
gitLocationEnvVars []string
} }
// Path to the current worktree. If we're in the main worktree, this will // Path to the current worktree. If we're in the main worktree, this will
// be the same as RepoPath() // be the same as RepoPath(). It is empty for a bare repo, which has no
// worktree at all.
func (self *RepoPaths) WorktreePath() string { func (self *RepoPaths) WorktreePath() string {
return self.worktreePath return self.worktreePath
} }
@ -53,10 +54,33 @@ func (self *RepoPaths) RepoName() string {
return self.repoName return self.repoName
} }
// Whether we found no worktree, so that there is nothing for lazygit to show.
// Note that this isn't quite git's core.bare: a repo that calls itself non-bare
// but whose worktree we couldn't find counts as bare for us too. Concretely,
// this is true when we're in
//
// - a genuinely bare repo;
// - the git dir of a linked worktree (.git/worktrees/x), whose worktree is
// recorded but not somewhere we look;
// - a repo that keeps its worktree somewhere only GIT_WORK_TREE knows, such
// as a vcsh-style dotfiles repo that hasn't been given core.worktree.
//
// The .git dir of an ordinary repo is not one of them: GetRepoPathsForDir
// notices the worktree holding it and hands back that repo instead.
func (self *RepoPaths) IsBareRepo() bool { func (self *RepoPaths) IsBareRepo() bool {
return self.isBareRepo return self.isBareRepo
} }
// The environment that tells git where this repo is, as "NAME=value" entries.
// It is empty for the vast majority of repos, which git finds for itself by
// looking for a .git in the directory a command runs in. It is only non-empty
// when that doesn't work — when the git dir lives somewhere else entirely,
// because of core.worktree or --work-tree — and then every command addressing
// the repo has to carry it.
func (self *RepoPaths) GitLocationEnvVars() []string {
return self.gitLocationEnvVars
}
// Returns the repo paths for a typical repo // Returns the repo paths for a typical repo
func MockRepoPaths(currentPath string) *RepoPaths { func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{ return &RepoPaths{
@ -84,26 +108,76 @@ func GetRepoPathsForDir(
dir string, dir string,
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
) (*RepoPaths, error) { ) (*RepoPaths, error) {
gitDirOutput, err := callGitRevParseWithDir(cmd, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree") repoPaths, err := repoPathsForDir(dir, cmd)
if err != nil || !repoPaths.IsBareRepo() {
return repoPaths, err
}
// We're in a git dir rather than in a working tree, which usually just means
// somebody ran lazygit in the .git of an ordinary repo. git's convention is
// that a git dir called .git belongs to the directory holding it, so look
// there: if that is a working tree, it is the repo we were asked about, and
// there's no reason to make the user go up a directory and try again.
//
// The git dirs that aren't called .git keep the paths we have. A linked
// worktree's (.git/worktrees/x) and a submodule's (.git/modules/x) do have a
// working tree, but only the directory holding a .git tells us where, so we
// would be guessing. A bare repo's has none to find.
if filepath.Base(repoPaths.WorktreeGitDirPath()) != ".git" {
return repoPaths, nil
}
pathsFromWorkTree, err := repoPathsForDir(filepath.Dir(repoPaths.WorktreeGitDirPath()), cmd)
if err != nil || pathsFromWorkTree.IsBareRepo() {
return repoPaths, nil
}
return pathsFromWorkTree, nil
}
// repoPathsForDir asks git about the repo at dir, and reports a bare repo when
// there is no working tree there. Unlike GetRepoPathsForDir it never looks
// anywhere but dir, which is what keeps that one from going round in circles.
func repoPathsForDir(
dir string,
cmd oscommands.ICmdObjBuilder,
) (*RepoPaths, error) {
gitDirOutput, err := callGitRevParseWithDir(cmd, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
if err != nil { if err != nil {
return nil, err // --show-toplevel is the only one of these that needs a work tree, and
// git makes it fatal when there isn't one. So this may just mean we're in
// a repo that has no work tree.
return getBareRepoPathsForDir(dir, cmd, err)
} }
gitDirResults := strings.Split(utils.NormalizeLinefeeds(gitDirOutput), "\n") gitDirResults := strings.Split(utils.NormalizeLinefeeds(gitDirOutput), "\n")
worktreePath := gitDirResults[0] worktreePath := gitDirResults[0]
worktreeGitDirPath := gitDirResults[1] worktreeGitDirPath := gitDirResults[1]
repoGitDirPath := gitDirResults[2] repoGitDirPath := gitDirResults[2]
isBareRepo := gitDirResults[3] == "true"
// If we're in a submodule, --show-superproject-working-tree will return // A worktree that has the repo's common git dir to itself is the repo's main
// a value, meaning gitDirResults will be length 5. In that case // worktree, so it is the repoPath. That holds for a submodule as well: its
// return the worktree path as the repoPath. Otherwise we're in a // git dir lives under the superproject's .git/modules, but it is still the
// normal repo or a worktree so return the parent of the git common // submodule's own common dir.
// dir (repoGitDirPath) isMainWorktree := worktreeGitDirPath == repoGitDirPath
isSubmodule := len(gitDirResults) == 5
// If we're in a submodule, --show-superproject-working-tree will return a
// value, meaning gitDirResults will be length 4. That only tells us anything
// new for a linked worktree of a submodule, which isMainWorktree misses.
isSubmodule := len(gitDirResults) == 4
// Otherwise we're in a linked worktree, and the repoPath is the repo's main
// worktree. git won't tell us where that is: `git worktree list` reports it
// as the common git dir with a trailing "/.git" removed, which is this same
// derivation. So take the directory holding the common git dir. That is the
// main worktree of an ordinary repo, and of a bare one it is the directory
// its worktrees live in. It is not the main worktree of a repo that moved
// that elsewhere with core.worktree; there we end up naming the git dir's
// directory, which means that the repo name we display in the status panel
// isn't correct, and we start looking for .lazygit.yml in the wrong place.
// Both of those are not severe enough to justify the extra git call to get
// the real main worktree, so we accept this for this rather niche use case.
var repoPath string var repoPath string
if isSubmodule { if isMainWorktree || isSubmodule {
repoPath = worktreePath repoPath = worktreePath
} else { } else {
repoPath = filepath.Dir(repoGitDirPath) repoPath = filepath.Dir(repoGitDirPath)
@ -116,62 +190,113 @@ func GetRepoPathsForDir(
repoPath: repoPath, repoPath: repoPath,
repoGitDirPath: repoGitDirPath, repoGitDirPath: repoGitDirPath,
repoName: repoName, repoName: repoName,
isBareRepo: isBareRepo, isBareRepo: false,
gitLocationEnvVars: gitLocationEnvVars(cmd, worktreePath, worktreeGitDirPath),
}, nil }, nil
} }
// gitLocationEnvVars works out whether git can find the repo by itself when a
// command runs in its worktree, and if it can't, returns the environment that
// tells git where it is. See RepoPaths.GitLocationEnvVars.
func gitLocationEnvVars(
cmd oscommands.ICmdObjBuilder,
worktreePath string,
worktreeGitDirPath string,
) []string {
// The ordinary repo, where the git dir sits in the worktree. Both paths are
// git's own answers from the same invocation, so they are spelled alike and
// comparing them is safe.
if worktreeGitDirPath == filepath.Join(worktreePath, ".git") {
return nil
}
// A linked worktree or a submodule instead has a .git file naming its git
// dir, and git follows that just as happily. We could read the file, but the
// path in it may well name the same directory differently than git did
// above, so ask git to resolve it — from the worktree and nothing else.
discoveredGitDirPath, err := callGitRevParseInOtherRepo(cmd, worktreePath, "--absolute-git-dir")
if err == nil && discoveredGitDirPath == worktreeGitDirPath {
return nil
}
return []string{
env.GitDirEnvVar + "=" + worktreeGitDirPath,
env.GitWorkTreeEnvVar + "=" + worktreePath,
}
}
// getBareRepoPathsForDir is the fallback for when we couldn't ask git for the
// work tree. Everything but --show-toplevel works fine without one, so if the
// remaining queries succeed we are in a bare repo, and we return what we know
// about it with an empty worktreePath. If they fail too we simply aren't in a
// repo, and the caller's original error says so better than ours would.
func getBareRepoPathsForDir(
dir string,
cmd oscommands.ICmdObjBuilder,
errWithWorktree error,
) (*RepoPaths, error) {
output, err := callGitRevParseWithDir(cmd, dir, "--absolute-git-dir", "--git-common-dir")
if err != nil {
return nil, errWithWorktree
}
results := strings.Split(utils.NormalizeLinefeeds(output), "\n")
repoGitDirPath := results[1]
// A bare repo has no worktree, and so no repo path in the sense the caller
// with a worktree means. It doesn't matter much what we say here, because
// nobody reads it: whoever is handed a bare repo either offers to open a
// recent one instead (app.setupRepo) or is turned away by NewGitCommand. The
// directory holding the git dir is the nearest thing there is to a repo
// path.
repoPath := filepath.Dir(repoGitDirPath)
return &RepoPaths{
worktreePath: "",
worktreeGitDirPath: results[0],
repoPath: repoPath,
repoGitDirPath: repoGitDirPath,
repoName: filepath.Base(repoPath),
isBareRepo: true,
}, nil
}
// Asks git about the repo at dir. This is how we find our own repo, so it has
// to be answered the way git itself would answer it there, GIT_DIR and
// GIT_WORK_TREE included.
func callGitRevParseWithDir( func callGitRevParseWithDir(
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
dir string, dir string,
gitRevArgs ...string, gitRevArgs ...string,
) (string, error) { ) (string, error) {
return runGitRevParse(newGitRevParseCmd(cmd, dir, gitRevArgs...))
}
// Asks git about a repo that isn't the one we have open; see forOtherRepo.
func callGitRevParseInOtherRepo(
cmd oscommands.ICmdObjBuilder,
dir string,
gitRevArgs ...string,
) (string, error) {
return runGitRevParse(forOtherRepo(newGitRevParseCmd(cmd, dir, gitRevArgs...)))
}
func newGitRevParseCmd(
cmd oscommands.ICmdObjBuilder,
dir string,
gitRevArgs ...string,
) *oscommands.CmdObj {
gitRevParse := NewGitCmd("rev-parse").Arg("--path-format=absolute").Arg(gitRevArgs...) gitRevParse := NewGitCmd("rev-parse").Arg("--path-format=absolute").Arg(gitRevArgs...)
if dir != "" { if dir != "" {
gitRevParse.Dir(dir) gitRevParse.Dir(dir)
} }
gitCmd := cmd.New(gitRevParse.ToArgv()).DontLog() return cmd.New(gitRevParse.ToArgv()).DontLog()
}
func runGitRevParse(gitCmd *oscommands.CmdObj) (string, error) {
res, err := gitCmd.RunWithOutput() res, err := gitCmd.RunWithOutput()
if err != nil { if err != nil {
return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err) return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err)
} }
return strings.TrimSpace(res), nil return strings.TrimSpace(res), nil
} }
// Returns the paths of linked worktrees
func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := fs.Stat(worktreeGitDirsPath)
if err != nil {
return result
}
_ = afero.Walk(fs, worktreeGitDirsPath, func(currPath string, info ioFs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
gitDirPath := filepath.Join(currPath, "gitdir")
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
if err != nil {
// ignoring error
return nil
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := filepath.Dir(trimmedGitDir)
result = append(result, worktreeDir)
return nil
})
return result
}

View file

@ -38,8 +38,6 @@ func TestGetRepoPaths(t *testing.T) {
`C:\path\to\repo\.git`, `C:\path\to\repo\.git`,
// --git-common-dir // --git-common-dir
`C:\path\to\repo\.git`, `C:\path\to\repo\.git`,
// --is-bare-repository
"false",
// --show-superproject-working-tree // --show-superproject-working-tree
}, []string{ }, []string{
// --show-toplevel // --show-toplevel
@ -48,12 +46,10 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git", "/path/to/repo/.git",
// --git-common-dir // --git-common-dir
"/path/to/repo/.git", "/path/to/repo/.git",
// --is-bare-repository
"false",
// --show-superproject-working-tree // --show-superproject-working-tree
}) })
runner.ExpectGitArgs( runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(mockOutput, "\n"), strings.Join(mockOutput, "\n"),
nil) nil)
}, },
@ -76,53 +72,147 @@ func TestGetRepoPaths(t *testing.T) {
Err: nil, Err: nil,
}, },
{ {
// git refuses to answer --show-toplevel when there's no work tree, so
// we have to ask a second time without it.
Name: "bare repo", Name: "bare repo",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
"",
errors.New("fatal: this operation must be run in a work tree"))
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{ mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir // --git-dir
`C:\path\to\bare_repo\bare.git`, `C:\path\to\project\bare.git`,
// --git-common-dir // --git-common-dir
`C:\path\to\bare_repo\bare.git`, `C:\path\to\project\bare.git`,
// --is-bare-repository
`true`,
// --show-superproject-working-tree
}, []string{ }, []string{
// --show-toplevel
"/path/to/repo",
// --git-dir // --git-dir
"/path/to/bare_repo/bare.git", "/path/to/project/bare.git",
// --git-common-dir // --git-common-dir
"/path/to/bare_repo/bare.git", "/path/to/project/bare.git",
// --is-bare-repository
"true",
// --show-superproject-working-tree
}) })
runner.ExpectGitArgs( runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), append(getRevParseArgs(), "--absolute-git-dir", "--git-common-dir"),
strings.Join(mockOutput, "\n"), strings.Join(mockOutput, "\n"),
nil) nil)
}, },
Path: "/path/to/repo", Path: "/path/to/project",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{ Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`, worktreePath: "",
worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`, worktreeGitDirPath: `C:\path\to\project\bare.git`,
repoPath: `C:\path\to\bare_repo`, repoPath: `C:\path\to\project`,
repoGitDirPath: `C:\path\to\bare_repo\bare.git`, repoGitDirPath: `C:\path\to\project\bare.git`,
repoName: `bare_repo`, repoName: `project`,
isBareRepo: true, isBareRepo: true,
}, &RepoPaths{ }, &RepoPaths{
worktreePath: "/path/to/repo", worktreePath: "",
worktreeGitDirPath: "/path/to/bare_repo/bare.git", worktreeGitDirPath: "/path/to/project/bare.git",
repoPath: "/path/to/bare_repo", repoPath: "/path/to/project",
repoGitDirPath: "/path/to/bare_repo/bare.git", repoGitDirPath: "/path/to/project/bare.git",
repoName: "bare_repo", repoName: "project",
isBareRepo: true, isBareRepo: true,
}), }),
Err: nil, Err: nil,
}, },
{
// Standing in the .git dir of an ordinary repo: git refuses to name a
// work tree, but the directory holding the .git is one, so we open the
// repo from there.
Name: "in a repo's .git dir",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
gitDir := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo\.git`, "/path/to/repo/.git")
worktree := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo`, "/path/to/repo")
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
"",
errors.New("fatal: this operation must be run in a work tree"))
runner.ExpectGitArgs(
append(getRevParseArgs(), "--absolute-git-dir", "--git-common-dir"),
strings.Join([]string{gitDir, gitDir}, "\n"),
nil)
// asking again from the directory holding the .git
runner.ExpectGitArgs(
append(append([]string{"-C", worktree}, getRevParseArgs()...), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join([]string{worktree, gitDir, gitDir}, "\n"),
nil)
},
Path: "/path/to/repo/.git",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\repo\.git`,
repoPath: `C:\path\to\repo`,
repoGitDirPath: `C:\path\to\repo\.git`,
repoName: `repo`,
isBareRepo: false,
}, &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/repo/.git",
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
isBareRepo: false,
}),
Err: nil,
},
{
// A repo whose work tree lives somewhere else entirely, as set up by
// core.worktree or by --work-tree. We're in the main worktree, but the
// git dir is not inside it.
Name: "repo with a separate work tree",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\worktree`,
// --git-dir
`C:\path\to\repo\.git`,
// --git-common-dir
`C:\path\to\repo\.git`,
// --show-superproject-working-tree
}, []string{
// --show-toplevel
"/path/to/worktree",
// --git-dir
"/path/to/repo/.git",
// --git-common-dir
"/path/to/repo/.git",
// --show-superproject-working-tree
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(mockOutput, "\n"),
nil)
// asking git to find the repo from the work tree gets us nowhere,
// because there is no .git there
worktree := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\worktree`, "/path/to/worktree")
runner.ExpectGitArgs(
append([]string{"-C", worktree}, append(getRevParseArgs(), "--absolute-git-dir")...),
"",
errors.New("fatal: not a git repository (or any of the parent directories): .git"))
},
Path: "/path/to/repo",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\worktree`,
worktreeGitDirPath: `C:\path\to\repo\.git`,
repoPath: `C:\path\to\worktree`,
repoGitDirPath: `C:\path\to\repo\.git`,
repoName: `worktree`,
isBareRepo: false,
gitLocationEnvVars: []string{`GIT_DIR=C:\path\to\repo\.git`, `GIT_WORK_TREE=C:\path\to\worktree`},
}, &RepoPaths{
worktreePath: "/path/to/worktree",
worktreeGitDirPath: "/path/to/repo/.git",
repoPath: "/path/to/worktree",
repoGitDirPath: "/path/to/repo/.git",
repoName: "worktree",
isBareRepo: false,
gitLocationEnvVars: []string{"GIT_DIR=/path/to/repo/.git", "GIT_WORK_TREE=/path/to/worktree"},
}),
Err: nil,
},
{ {
Name: "submodule", Name: "submodule",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
@ -133,8 +223,6 @@ func TestGetRepoPaths(t *testing.T) {
`C:\path\to\repo\.git\modules\submodule1`, `C:\path\to\repo\.git\modules\submodule1`,
// --git-common-dir // --git-common-dir
`C:\path\to\repo\.git\modules\submodule1`, `C:\path\to\repo\.git\modules\submodule1`,
// --is-bare-repository
`false`,
// --show-superproject-working-tree // --show-superproject-working-tree
`C:\path\to\repo`, `C:\path\to\repo`,
}, []string{ }, []string{
@ -144,15 +232,22 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git/modules/submodule1", "/path/to/repo/.git/modules/submodule1",
// --git-common-dir // --git-common-dir
"/path/to/repo/.git/modules/submodule1", "/path/to/repo/.git/modules/submodule1",
// --is-bare-repository
"false",
// --show-superproject-working-tree // --show-superproject-working-tree
"/path/to/repo", "/path/to/repo",
}) })
runner.ExpectGitArgs( runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(mockOutput, "\n"), strings.Join(mockOutput, "\n"),
nil) nil)
// git finds the submodule's git dir from its work tree, via the
// .git file there
worktree := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo\submodule1`, "/path/to/repo/submodule1")
gitDir := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo\.git\modules\submodule1`, "/path/to/repo/.git/modules/submodule1")
runner.ExpectGitArgs(
append([]string{"-C", worktree}, append(getRevParseArgs(), "--absolute-git-dir")...),
gitDir,
nil)
}, },
Path: "/path/to/repo/submodule1", Path: "/path/to/repo/submodule1",
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{ Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
@ -176,7 +271,12 @@ func TestGetRepoPaths(t *testing.T) {
Name: "git rev-parse returns an error", Name: "git rev-parse returns an error",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
runner.ExpectGitArgs( runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"), append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
"",
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
// we're not in a repo at all, so asking about a bare one fails too
runner.ExpectGitArgs(
append(getRevParseArgs(), "--absolute-git-dir", "--git-common-dir"),
"", "",
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git")) errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
}, },
@ -184,7 +284,7 @@ func TestGetRepoPaths(t *testing.T) {
Expected: nil, Expected: nil,
Err: func(getRevParseArgs argFn) error { Err: func(getRevParseArgs argFn) error {
args := strings.Join(getRevParseArgs(), " ") args := strings.Join(getRevParseArgs(), " ")
return fmt.Errorf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args) return fmt.Errorf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args)
}, },
}, },
} }

View file

@ -81,20 +81,13 @@ func (self *StashCommands) Hash(index int) (string, error) {
} }
func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj { func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj {
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
// "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason // "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason
cmdArgs := NewGitCmd("stash").Arg("show"). cmdArgs := NewGitCmd("stash").Arg("show").
AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), true).
Arg("-p"). Arg("-p").
Arg("--stat"). Arg("--stat").
Arg("-u"). Arg("-u").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd). Arg(fmt.Sprintf("--color=%s", self.diffRendererConfigManager.GetColorArg())).
ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
Arg(fmt.Sprintf("refs/stash@{%d}", index)). Arg(fmt.Sprintf("refs/stash@{%d}", index)).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()

View file

@ -103,7 +103,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize uint64 contextSize uint64
similarityThreshold int similarityThreshold int
ignoreWhitespace bool ignoreWhitespace bool
pagerConfig *config.PagingConfig diffRendererConfig *config.DiffRendererConfig
expected []string expected []string
} }
@ -114,7 +114,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@ -122,7 +122,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 77, contextSize: 77,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "--no-ext-diff", "--unified=77", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
{ {
testName: "Show diff with custom similarity threshold", testName: "Show diff with custom similarity threshold",
@ -130,7 +130,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 33, similarityThreshold: 33,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "--no-ext-diff", "--unified=3", "--find-renames=33%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
{ {
testName: "Show diff with external diff command", testName: "Show diff with external diff command",
@ -138,8 +138,8 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"}, diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
{ {
testName: "Show diff using git's external diff config", testName: "Show diff using git's external diff config",
@ -147,16 +147,16 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: false, ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true}, diffRendererConfig: &config.DiffRendererConfig{Type: "extDiff"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "--ext-diff", "--unified=3", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
{ {
testName: "Default case", testName: "Ignore whitespace",
index: 5, index: 5,
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
ignoreWhitespace: true, ignoreWhitespace: true,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"}, expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "-p", "--stat", "-u", "--color=always", "refs/stash@{5}"},
}, },
} }
@ -166,8 +166,8 @@ func TestStashStashEntryCmdObj(t *testing.T) {
userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
userConfig.Git.DiffContextSize = s.contextSize userConfig.Git.DiffContextSize = s.contextSize
userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold
if s.pagerConfig != nil { if s.diffRendererConfig != nil {
userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig} userConfig.Git.DiffRenderers = []config.DiffRendererConfig{*s.diffRendererConfig}
} }
repoPaths := RepoPaths{ repoPaths := RepoPaths{
worktreePath: "/path/to/worktree", worktreePath: "/path/to/worktree",

View file

@ -4,8 +4,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/spf13/afero"
) )
type StatusCommands struct { type StatusCommands struct {
@ -82,6 +84,66 @@ func (self *StatusCommands) IsInRevert() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "REVERT_HEAD")) return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "REVERT_HEAD"))
} }
// RefsSnapshot returns a string fingerprint of the current state of local
// branches and HEAD. Comparing two snapshots byte-for-byte tells us whether
// any local ref or HEAD has moved since the last snapshot.
func (self *StatusCommands) RefsSnapshot() (string, error) {
t := time.Now()
defer func() { self.Log.Infof("RefsSnapshot took %s", time.Since(t)) }()
refsArgs := NewGitCmd("for-each-ref").
Arg("--format=%(objectname) %(refname)").
Arg("refs/heads").
ToArgv()
refs, err := self.cmd.New(refsArgs).DontLog().RunWithOutput()
if err != nil {
return "", err
}
head, err := self.headSnapshot()
if err != nil {
return "", err
}
return refs + head, nil
}
// headSnapshot returns a fingerprint of HEAD that distinguishes "detached at
// commit X" from "on a branch that points at X". The commit hash alone can't
// tell those apart, which matters at the end of a rebase: HEAD reattaches to
// the branch without the hash changing, and we'd otherwise miss that refresh.
//
// We read .git/HEAD directly rather than shelling out: it's faster (no child
// process) and its content is exactly the symref-or-hash distinction we want
// ("ref: refs/heads/foo" when attached, the raw hash when detached). The
// reftable backend, however, doesn't keep a real .git/HEAD — it writes a fixed
// stub ("ref: refs/heads/.invalid") that never reflects the actual HEAD. When
// we see that stub (or the file is missing/unreadable) we fall back to
// porcelain commands, which are backend-agnostic.
func (self *StatusCommands) headSnapshot() (string, error) {
headPath := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "HEAD")
if content, err := afero.ReadFile(self.Fs, headPath); err == nil {
head := strings.TrimSpace(string(content))
if head != "" && head != "ref: refs/heads/.invalid" {
return head, nil
}
}
// symbolic-ref gives the branch when HEAD is attached and fails when it's
// detached, in which case rev-parse gives the commit HEAD points at.
symbolicRefArgs := NewGitCmd("symbolic-ref").Arg("HEAD").ToArgv()
if symref, err := self.cmd.New(symbolicRefArgs).DontLog().RunWithOutput(); err == nil {
return strings.TrimSpace(symref), nil
}
revParseArgs := NewGitCmd("rev-parse").Arg("HEAD").ToArgv()
head, err := self.cmd.New(revParseArgs).DontLog().RunWithOutput()
if err != nil {
return "", err
}
return strings.TrimSpace(head), nil
}
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently // Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
// being rebased, or empty string when we're not in a rebase // being rebased, or empty string when we're not in a rebase
func (self *StatusCommands) BranchBeingRebased() string { func (self *StatusCommands) BranchBeingRebased() string {

View file

@ -0,0 +1,91 @@
package git_commands
import (
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)
func TestStatusRefsSnapshot(t *testing.T) {
const forEachRefOutput = "aaaa refs/heads/main\nbbbb refs/heads/topic\n"
forEachRefArgs := []string{"for-each-ref", "--format=%(objectname) %(refname)", "refs/heads"}
scenarios := []struct {
testName string
headFile *string // nil means: don't create a .git/HEAD file (simulates it being unreadable).
runner *oscommands.FakeCmdObjRunner
expectedHead string
}{
{
// files backend, on a branch: read straight from .git/HEAD, no
// child process for HEAD.
testName: "attached, read from HEAD file",
headFile: lo.ToPtr("ref: refs/heads/main\n"),
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(forEachRefArgs, forEachRefOutput, nil),
expectedHead: "ref: refs/heads/main",
},
{
// files backend, detached: .git/HEAD holds the raw hash.
testName: "detached, read from HEAD file",
headFile: lo.ToPtr("aaaa\n"),
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(forEachRefArgs, forEachRefOutput, nil),
expectedHead: "aaaa",
},
{
// reftable backend (HEAD is a fixed stub), attached: fall back to
// symbolic-ref, which succeeds.
testName: "reftable stub, attached, fall back to symbolic-ref",
headFile: lo.ToPtr("ref: refs/heads/.invalid\n"),
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(forEachRefArgs, forEachRefOutput, nil).
ExpectGitArgs([]string{"symbolic-ref", "HEAD"}, "refs/heads/main\n", nil),
expectedHead: "refs/heads/main",
},
{
// reftable backend, detached: symbolic-ref fails, fall back to
// rev-parse.
testName: "reftable stub, detached, fall back to rev-parse",
headFile: lo.ToPtr("ref: refs/heads/.invalid\n"),
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(forEachRefArgs, forEachRefOutput, nil).
ExpectGitArgs([]string{"symbolic-ref", "HEAD"}, "", errors.New("fatal: ref HEAD is not a symbolic ref")).
ExpectGitArgs([]string{"rev-parse", "HEAD"}, "aaaa\n", nil),
expectedHead: "aaaa",
},
{
// HEAD file missing/unreadable: same fallback as reftable.
testName: "no HEAD file, fall back to symbolic-ref",
headFile: nil,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(forEachRefArgs, forEachRefOutput, nil).
ExpectGitArgs([]string{"symbolic-ref", "HEAD"}, "refs/heads/main\n", nil),
expectedHead: "refs/heads/main",
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
fs := afero.NewMemMapFs()
if s.headFile != nil {
assert.NoError(t, afero.WriteFile(fs, "/repo/.git/HEAD", []byte(*s.headFile), 0o600))
}
instance := buildStatusCommands(commonDeps{
runner: s.runner,
fs: fs,
repoPaths: MockRepoPaths("/repo"),
})
snapshot, err := instance.RefsSnapshot()
assert.NoError(t, err)
assert.Equal(t, forEachRefOutput+s.expectedHead, snapshot)
s.runner.CheckForMissingCalls()
})
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
) )
// .gitmodules looks like this: // .gitmodules looks like this:
@ -27,10 +28,15 @@ func NewSubmoduleCommands(gitCommon *GitCommon) *SubmoduleCommands {
} }
func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig) ([]*models.SubmoduleConfig, error) { func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig) ([]*models.SubmoduleConfig, error) {
gitModulesPath := ".gitmodules" // Resolve the path against the repo this commands object was created for
// rather than the process working directory, so that a read from a
// still-running refresh keeps addressing that repo after the user
// switched to another one.
dir := self.repoPaths.WorktreePath()
if parentModule != nil { if parentModule != nil {
gitModulesPath = filepath.Join(parentModule.FullPath(), gitModulesPath) dir = filepath.Join(dir, parentModule.FullPath())
} }
gitModulesPath := filepath.Join(dir, ".gitmodules")
file, err := os.Open(gitModulesPath) file, err := os.Open(gitModulesPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -86,10 +92,100 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
return configs, nil return configs, nil
} }
// AnyHaveStageableChanges reports whether any of the given submodule paths has
// a checked-out commit that differs from the one recorded in the
// superproject's index, i.e. a change that `git add <path>` would actually
// stage. A submodule that only has dirty or untracked content (with no new
// commit) can't be staged from the superproject, so it won't be reported here.
func (self *SubmoduleCommands) AnyHaveStageableChanges(paths []string) (bool, error) {
if len(paths) == 0 {
return false, nil
}
cmdArgs := NewGitCmd("submodule").Arg("status", "--").Arg(paths...).ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return false, err
}
// Each line looks like "<prefix><sha> <path> (<describe>)". A '+' prefix
// means the checked-out commit differs from the index, i.e. there's a
// commit change to stage.
return lo.SomeBy(strings.Split(output, "\n"), func(line string) bool {
return strings.HasPrefix(line, "+")
}), nil
}
// GetConflictCommits returns the three gitlink commits of a conflicted submodule
// from the index: the merge base, our (current) commit, and their (incoming)
// commit. Any of them can be empty if that stage is absent (e.g. a submodule
// that was added on only one side). The path is relative to the repo root.
func (self *SubmoduleCommands) GetConflictCommits(path string) (base string, ours string, theirs string, err error) {
cmdArgs := NewGitCmd("ls-files").Arg("-u", "-z", "--", path).ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return "", "", "", err
}
// Each NUL-terminated entry looks like "<mode> <sha> <stage>\t<path>".
for _, entry := range strings.Split(output, "\x00") {
// fields are split on the tab and the spaces, so the leading three are
// always mode, sha, stage regardless of what the path contains.
fields := strings.Fields(entry)
if len(fields) < 3 {
continue
}
switch fields[2] {
case "1":
base = fields[1]
case "2":
ours = fields[1]
case "3":
theirs = fields[1]
}
}
return base, ours, theirs, nil
}
// GetCommitSummary returns "<short-sha> <subject>" for a commit inside the
// submodule at the given path, for display in the conflict menu.
func (self *SubmoduleCommands) GetCommitSummary(path string, sha string) (string, error) {
cmdArgs := NewGitCmd("log").
Dir(path).
Arg("--format=%h %s", "--max-count=1", sha).
Config("log.showsignature=false").
ToArgv()
summary, err := forOtherRepo(self.cmd.New(cmdArgs)).DontLog().RunWithOutput()
return strings.TrimSpace(summary), err
}
// CheckoutConflictCommit resolves a submodule conflict by checking the submodule
// out at the given commit. `git checkout --ours/--theirs` is a no-op on
// gitlinks, so we check out the chosen commit in the submodule itself; the
// caller then stages the submodule to record the resolution.
func (self *SubmoduleCommands) CheckoutConflictCommit(path string, sha string) error {
cmdArgs := NewGitCmd("checkout").Dir(path).Arg(sha).ToArgv()
return forOtherRepo(self.cmd.New(cmdArgs)).Run()
}
// ConflictSideLog returns a oneline log, run inside the submodule, of the commits
// that `side` has but `otherSide` does not (i.e. `otherSide..side`) — the commits
// unique to one side of a commit conflict, relative to their common ancestor. It
// is empty if `side` is an ancestor of `otherSide` (e.g. that side was rewound).
func (self *SubmoduleCommands) ConflictSideLog(path string, side string, otherSide string) (string, error) {
cmdArgs := NewGitCmd("log").Dir(path).
Arg("--oneline", "--color=always", otherSide+".."+side).
ToArgv()
return forOtherRepo(self.cmd.New(cmdArgs)).DontLog().RunWithOutput()
}
func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
// if the path does not exist then it hasn't yet been initialized so we'll swallow the error // if the path does not exist then it hasn't yet been initialized so we'll swallow the error
// because the intention here is to have no dirty worktree state // because the intention here is to have no dirty worktree state
if _, err := os.Stat(submodule.Path); os.IsNotExist(err) { if _, err := os.Stat(filepath.Join(self.repoPaths.WorktreePath(), submodule.FullPath())); os.IsNotExist(err) {
self.Log.Infof("submodule path %s does not exist, returning", submodule.FullPath()) self.Log.Infof("submodule path %s does not exist, returning", submodule.FullPath())
return nil return nil
} }
@ -99,20 +195,15 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
Arg("--include-untracked"). Arg("--include-untracked").
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return forOtherRepo(self.cmd.New(cmdArgs)).Run()
} }
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
parentDir := ""
if submodule.ParentModule != nil {
parentDir = submodule.ParentModule.FullPath()
}
cmdArgs := NewGitCmd("submodule"). cmdArgs := NewGitCmd("submodule").
Arg("update", "--init", "--force", "--", submodule.Path). Arg("update", "--init", "--force", "--", submodule.Path).
DirIf(parentDir != "", parentDir).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.runInParentModule(submodule, self.cmd.New(cmdArgs))
} }
func (self *SubmoduleCommands) UpdateAll() error { func (self *SubmoduleCommands) UpdateAll() error {
@ -122,51 +213,58 @@ func (self *SubmoduleCommands) UpdateAll() error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
// runInParentModule runs the given command in the submodule's parent module's
// directory when the submodule is nested: its path arguments (and the
// .gitmodules file the config commands touch) are relative to the parent
// module. The directory is set on the command itself rather than by
// temporarily chdir-ing the process there, which would leak the parent
// module's directory into whatever other commands run concurrently (e.g. a
// background refresh's).
//
// That directory is relative, so it resolves against the process working
// directory rather than against the repo directory the command builder
// otherwise pins commands to. Only foreground commands the user issued end up
// here, and lazygit won't switch repos while one of those is in flight, so the
// two are the same directory; don't call this from background work, where they
// need not be.
func (self *SubmoduleCommands) runInParentModule(submodule *models.SubmoduleConfig, cmdObj *oscommands.CmdObj) error {
if submodule.ParentModule != nil {
forOtherRepo(cmdObj.SetWd(submodule.ParentModule.FullPath()))
}
return cmdObj.Run()
}
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677 // based on https://gist.github.com/myusuf3/7f645819ded92bda6677
if submodule.ParentModule != nil { if err := self.runInParentModule(submodule, self.cmd.New(
wd, err := os.Getwd()
if err != nil {
return err
}
err = os.Chdir(submodule.ParentModule.FullPath())
if err != nil {
return err
}
defer func() { _ = os.Chdir(wd) }()
}
if err := self.cmd.New(
NewGitCmd("submodule"). NewGitCmd("submodule").
Arg("deinit", "--force", "--", submodule.Path).ToArgv(), Arg("deinit", "--force", "--", submodule.Path).ToArgv(),
).Run(); err != nil { )); err != nil {
if !strings.Contains(err.Error(), "did not match any file(s) known to git") { if !strings.Contains(err.Error(), "did not match any file(s) known to git") {
return err return err
} }
if err := self.cmd.New( if err := self.runInParentModule(submodule, self.cmd.New(
NewGitCmd("config"). NewGitCmd("config").
Arg("--file", ".gitmodules", "--remove-section", "submodule."+submodule.Path). Arg("--file", ".gitmodules", "--remove-section", "submodule."+submodule.Path).
ToArgv(), ToArgv(),
).Run(); err != nil { )); err != nil {
return err return err
} }
if err := self.cmd.New( if err := self.runInParentModule(submodule, self.cmd.New(
NewGitCmd("config"). NewGitCmd("config").
Arg("--remove-section", "submodule."+submodule.Path). Arg("--remove-section", "submodule."+submodule.Path).
ToArgv(), ToArgv(),
).Run(); err != nil { )); err != nil {
return err return err
} }
} }
if err := self.cmd.New( if err := self.runInParentModule(submodule, self.cmd.New(
NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToArgv(), NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToArgv(),
).Run(); err != nil { )); err != nil {
// if the directory isn't there then that's fine // if the directory isn't there then that's fine
self.Log.Error(err) self.Log.Error(err)
} }
@ -191,20 +289,6 @@ func (self *SubmoduleCommands) Add(name string, path string, url string) error {
} }
func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newUrl string) error { func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newUrl string) error {
if submodule.ParentModule != nil {
wd, err := os.Getwd()
if err != nil {
return err
}
err = os.Chdir(submodule.ParentModule.FullPath())
if err != nil {
return err
}
defer func() { _ = os.Chdir(wd) }()
}
setUrlCmdStr := NewGitCmd("config"). setUrlCmdStr := NewGitCmd("config").
Arg( Arg(
"--file", ".gitmodules", "submodule."+submodule.Name+".url", newUrl, "--file", ".gitmodules", "submodule."+submodule.Name+".url", newUrl,
@ -212,14 +296,14 @@ func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newU
ToArgv() ToArgv()
// the set-url command is only for later git versions so we're doing it manually here // the set-url command is only for later git versions so we're doing it manually here
if err := self.cmd.New(setUrlCmdStr).Run(); err != nil { if err := self.runInParentModule(submodule, self.cmd.New(setUrlCmdStr)); err != nil {
return err return err
} }
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", submodule.Path). syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", submodule.Path).
ToArgv() ToArgv()
if err := self.cmd.New(syncCmdStr).Run(); err != nil { if err := self.runInParentModule(submodule, self.cmd.New(syncCmdStr)); err != nil {
return err return err
} }

View file

@ -0,0 +1,116 @@
package git_commands
import (
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
func TestSubmoduleGetConflictCommits(t *testing.T) {
type scenario struct {
testName string
output string
expectedBase string
expectedOurs string
expectedTheirs string
}
scenarios := []scenario{
{
testName: "all three stages present (both modified)",
output: "160000 aaaaaaa 1\tmysub\x00160000 bbbbbbb 2\tmysub\x00160000 ccccccc 3\tmysub\x00",
expectedBase: "aaaaaaa",
expectedOurs: "bbbbbbb",
expectedTheirs: "ccccccc",
},
{
testName: "only our and their stages (added on both sides)",
output: "160000 bbbbbbb 2\tmysub\x00160000 ccccccc 3\tmysub\x00",
expectedBase: "",
expectedOurs: "bbbbbbb",
expectedTheirs: "ccccccc",
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"ls-files", "-u", "-z", "--", "mysub"}, s.output, nil)
instance := buildSubmoduleCommands(commonDeps{runner: runner})
base, ours, theirs, err := instance.GetConflictCommits("mysub")
assert.NoError(t, err)
assert.Equal(t, s.expectedBase, base)
assert.Equal(t, s.expectedOurs, ours)
assert.Equal(t, s.expectedTheirs, theirs)
runner.CheckForMissingCalls()
})
}
}
func TestSubmoduleGetConflictCommitsError(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"ls-files", "-u", "-z", "--", "mysub"}, "", errors.New("error"))
instance := buildSubmoduleCommands(commonDeps{runner: runner})
_, _, _, err := instance.GetConflictCommits("mysub")
assert.Error(t, err)
runner.CheckForMissingCalls()
}
func TestSubmoduleGetCommitSummary(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", "log.showsignature=false", "-C", "mysub", "log", "--format=%h %s", "--max-count=1", "bbbbbbb"}, "bbbbbbb the subject\n", nil)
instance := buildSubmoduleCommands(commonDeps{runner: runner})
summary, err := instance.GetCommitSummary("mysub", "bbbbbbb")
assert.NoError(t, err)
assert.Equal(t, "bbbbbbb the subject", summary)
runner.CheckForMissingCalls()
}
func TestSubmoduleCheckoutConflictCommit(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "mysub", "checkout", "bbbbbbb"}, "", nil)
instance := buildSubmoduleCommands(commonDeps{runner: runner})
assert.NoError(t, instance.CheckoutConflictCommit("mysub", "bbbbbbb"))
runner.CheckForMissingCalls()
}
// A command that runs inside a submodule mustn't inherit the GIT_DIR and
// GIT_WORK_TREE that say where the superproject is; git would answer it from
// there instead, and the answer would look perfectly plausible.
func TestSubmoduleCommandDoesntUseOurGitLocation(t *testing.T) {
t.Setenv(env.GitDirEnvVar, "/path/to/repo/.git")
t.Setenv(env.GitWorkTreeEnvVar, "/path/to/repo")
runner := oscommands.NewFakeRunner(t).
ExpectFunc("has neither GIT_DIR nor GIT_WORK_TREE", func(cmdObj *oscommands.CmdObj) bool {
return lo.NoneBy(cmdObj.GetEnvVars(), func(envVar string) bool {
return strings.HasPrefix(envVar, env.GitDirEnvVar+"=") ||
strings.HasPrefix(envVar, env.GitWorkTreeEnvVar+"=")
})
}, "bbbbbbb the subject\n", nil)
instance := buildSubmoduleCommands(commonDeps{runner: runner})
_, err := instance.GetCommitSummary("mysub", "bbbbbbb")
assert.NoError(t, err)
runner.CheckForMissingCalls()
}
func TestSubmoduleConflictSideLog(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "mysub", "log", "--oneline", "--color=always", "ccccccc..bbbbbbb"}, "bbbbbbb left\n", nil)
instance := buildSubmoduleCommands(commonDeps{runner: runner})
output, err := instance.ConflictSideLog("mysub", "bbbbbbb", "ccccccc")
assert.NoError(t, err)
assert.Equal(t, "bbbbbbb left\n", output)
runner.CheckForMissingCalls()
}

View file

@ -43,7 +43,7 @@ func (self *TagCommands) HasTag(tagName string) bool {
Arg("refs/tags/" + tagName). Arg("refs/tags/" + tagName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() == nil return self.cmd.New(cmdArgs).DontLog().Run() == nil
} }
func (self *TagCommands) LocalDelete(tagName string) error { func (self *TagCommands) LocalDelete(tagName string) error {
@ -74,7 +74,7 @@ func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) {
Arg("refs/tags/" + tagName). Arg("refs/tags/" + tagName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
func (self *TagCommands) IsTagAnnotated(tagName string) (bool, error) { func (self *TagCommands) IsTagAnnotated(tagName string) (bool, error) {
@ -83,6 +83,6 @@ func (self *TagCommands) IsTagAnnotated(tagName string) (bool, error) {
Arg("refs/tags/" + tagName). Arg("refs/tags/" + tagName).
ToArgv() ToArgv()
output, err := self.cmd.New(cmdArgs).RunWithOutput() output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.TrimSpace(output) == "tag", err return strings.TrimSpace(output) == "tag", err
} }

View file

@ -385,45 +385,31 @@ func (self *WorkingTreeCommands) Exclude(filename string) error {
// WorktreeFileDiff returns the diff of a file // WorktreeFileDiff returns the diff of a file
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string { func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string {
// for now we assume an error means the file was deleted // for now we assume an error means the file was deleted
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, nil).RunWithOutput() s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, file.Names()).RunWithOutput()
return s return s
} }
// WorktreeFileDiffCmdObj returns a command object for diffing a file or directory // WorktreeFileDiffCmdObj returns a command object for diffing the given paths
// in the working tree. When pathOverrides is non-empty, those paths are used instead of // in the working tree. node is the item they belong to; all it decides is
// the node's path (used to diff only filtered/visible files within a directory). // whether git has to compare against /dev/null, which is the case for a file
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, pathOverrides []string) *oscommands.CmdObj { // that isn't in the index yet.
colorArg := self.pagerConfig.GetColorArg() func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, paths []string) *oscommands.CmdObj {
colorArg := self.diffRendererConfigManager.GetColorArg()
if plain { if plain {
colorArg = "never" colorArg = "never"
} }
contextSize := self.UserConfig().Git.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != "" && !plain
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain
paths := pathOverrides
if len(paths) == 0 {
paths = []string{node.GetPath()}
}
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd). AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), !plain).
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)). Arg(fmt.Sprintf("--color=%s", colorArg)).
ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
ArgIf(cached, "--cached"). ArgIf(cached, "--cached").
ArgIf(noIndex, "--no-index"). ArgIf(noIndex, "--no-index").
Arg("--"). Arg("--").
ArgIf(noIndex, "/dev/null"). ArgIf(noIndex, "/dev/null").
Arg(paths...). Arg(paths...).
ArgIf(prevPath != "", prevPath).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()
@ -432,34 +418,30 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc // ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode. // but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { // For a renamed file, previousPath is the path it was renamed from (empty otherwise);
return self.ShowFileDiffCmdObj(from, to, reverse, []string{fileName}, plain).RunWithOutput() // both paths must be passed to git for the rename to be detected.
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, previousPath string, plain bool) (string, error) {
fileNames := []string{fileName}
if previousPath != "" {
fileNames = append(fileNames, previousPath)
}
return self.ShowFileDiffCmdObj(from, to, reverse, fileNames, plain).RunWithOutput()
} }
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileNames []string, plain bool) *oscommands.CmdObj { func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileNames []string, plain bool) *oscommands.CmdObj {
contextSize := self.UserConfig().Git.DiffContextSize colorArg := self.diffRendererConfigManager.GetColorArg()
colorArg := self.pagerConfig.GetColorArg()
if plain { if plain {
colorArg = "never" colorArg = "never"
} }
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != "" && !plain
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Config("diff.noprefix=false"). Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd). AddCommonDiffArgs(self.diffRendererConfigManager, self.UserConfig(), !plain).
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--no-renames").
Arg(fmt.Sprintf("--color=%s", colorArg)). Arg(fmt.Sprintf("--color=%s", colorArg)).
Arg(from). Arg(from).
Arg(to). Arg(to).
ArgIf(reverse, "-R"). ArgIf(reverse, "-R").
ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg("--"). Arg("--").
Arg(fileNames...). Arg(fileNames...).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).

View file

@ -221,7 +221,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "cached", testName: "cached",
@ -236,7 +236,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--cached", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "plain", testName: "plain",
@ -251,7 +251,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=never", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "File not tracked and file has no staged changes", testName: "File not tracked and file has no staged changes",
@ -266,7 +266,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=50%", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
@ -281,7 +281,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@ -296,7 +296,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 17, contextSize: 17,
similarityThreshold: 50, similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--find-renames=50%", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=17", "--find-renames=50%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom similarity threshold", testName: "Show diff with custom similarity threshold",
@ -311,7 +311,7 @@ func TestWorkingTreeDiff(t *testing.T) {
contextSize: 3, contextSize: 3,
similarityThreshold: 33, similarityThreshold: 33,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--find-renames=33%", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--unified=3", "--find-renames=33%", "--submodule", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
} }
@ -339,6 +339,8 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
from string from string
to string to string
reverse bool reverse bool
fileName string
previousPath string
plain bool plain bool
ignoreWhitespace bool ignoreWhitespace bool
contextSize uint64 contextSize uint64
@ -353,33 +355,49 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
from: "1234567890", from: "1234567890",
to: "0987654321", to: "0987654321",
reverse: false, reverse: false,
fileName: "test.txt",
plain: false, plain: false,
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
from: "1234567890", from: "1234567890",
to: "0987654321", to: "0987654321",
reverse: false, reverse: false,
fileName: "test.txt",
plain: false, plain: false,
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 123, contextSize: 123,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=123", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
from: "1234567890", from: "1234567890",
to: "0987654321", to: "0987654321",
reverse: false, reverse: false,
fileName: "test.txt",
plain: false, plain: false,
ignoreWhitespace: true, ignoreWhitespace: true,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--ignore-all-space", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Renamed file passes both paths so the rename is detected",
from: "1234567890",
to: "0987654321",
reverse: false,
fileName: "new.txt",
previousPath: "old.txt",
plain: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--unified=3", "--find-renames=50%", "--submodule", "--color=always", "1234567890", "0987654321", "--", "new.txt", "old.txt"}, expectedResult, nil),
}, },
} }
@ -394,7 +412,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths})
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain) result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, s.fileName, s.previousPath, s.plain)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, result) assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()

View file

@ -51,7 +51,7 @@ func (self *WorktreeCommands) Delete(worktreePath string, force bool) error {
func (self *WorktreeCommands) Detach(worktreePath string) error { func (self *WorktreeCommands) Detach(worktreePath string) error {
cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv() cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv()
return self.cmd.New(cmdArgs).Run() return forOtherRepo(self.cmd.New(cmdArgs)).Run()
} }
func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) { func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) {

View file

@ -22,9 +22,6 @@ func NewWorktreeLoader(gitCommon *GitCommon) *WorktreeLoader {
} }
func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
currentRepoPath := self.repoPaths.RepoPath()
worktreePath := self.repoPaths.WorktreePath()
cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv() cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv()
worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
@ -54,17 +51,13 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
if strings.HasPrefix(splitLine, "worktree ") { if strings.HasPrefix(splitLine, "worktree ") {
path := strings.SplitN(splitLine, " ", 2)[1] path := strings.SplitN(splitLine, " ", 2)[1]
isMain := path == currentRepoPath
isCurrent := path == worktreePath
isPathMissing := self.pathExists(path)
current = &models.Worktree{ current = &models.Worktree{
IsMain: isMain, IsPathMissing: self.pathExists(path),
IsCurrent: isCurrent,
IsPathMissing: isPathMissing,
Path: path, Path: path,
// we defer populating GitDir until a loop below so that // we defer populating GitDir until a loop below so that
// we can parallelize the calls to git rev-parse // we can parallelize the calls to git rev-parse, and
// IsMain/IsCurrent because they are derived from GitDir
GitDir: "", GitDir: "",
} }
} else if strings.HasPrefix(splitLine, "HEAD ") { } else if strings.HasPrefix(splitLine, "HEAD ") {
@ -84,7 +77,7 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
if worktree.IsPathMissing { if worktree.IsPathMissing {
return return
} }
gitDir, err := callGitRevParseWithDir(self.cmd, worktree.Path, "--absolute-git-dir") gitDir, err := callGitRevParseInOtherRepo(self.cmd, worktree.Path, "--absolute-git-dir")
if err != nil { if err != nil {
self.Log.Warnf("Could not find git dir for worktree %s: %v", worktree.Path, err) self.Log.Warnf("Could not find git dir for worktree %s: %v", worktree.Path, err)
return return
@ -95,6 +88,23 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
} }
wg.Wait() wg.Wait()
// Identify the current and the main worktree by their git dir rather than by
// their path: `git worktree list` reports the main worktree as the common
// git dir with a trailing "/.git" removed, which is the working tree only
// when the git dir sits inside it. In a submodule, a bare repo or a repo
// using core.worktree it doesn't, and comparing paths then matches nothing.
// A worktree whose directory is gone has no git dir to compare, so there we
// have nothing better than its path.
for _, worktree := range worktrees {
if worktree.GitDir != "" {
worktree.IsCurrent = worktree.GitDir == self.repoPaths.WorktreeGitDirPath()
worktree.IsMain = worktree.GitDir == self.repoPaths.RepoGitDirPath()
} else {
worktree.IsCurrent = worktree.Path == self.repoPaths.WorktreePath()
worktree.IsMain = worktree.Path == self.repoPaths.RepoPath()
}
}
names := getUniqueNamesFromPaths(lo.Map(worktrees, func(worktree *models.Worktree, _ int) string { names := getUniqueNamesFromPaths(lo.Map(worktrees, func(worktree *models.Worktree, _ int) string {
return worktree.Path return worktree.Path
})) }))

View file

@ -23,8 +23,10 @@ func TestGetWorktrees(t *testing.T) {
{ {
testName: "Single worktree (main)", testName: "Single worktree (main)",
repoPaths: &RepoPaths{ repoPaths: &RepoPaths{
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
worktreePath: "/path/to/repo", worktreePath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
worktreeGitDirPath: "/path/to/repo/.git",
}, },
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
@ -55,8 +57,10 @@ branch refs/heads/mybranch
{ {
testName: "Multiple worktrees (main + linked)", testName: "Multiple worktrees (main + linked)",
repoPaths: &RepoPaths{ repoPaths: &RepoPaths{
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
worktreePath: "/path/to/repo", worktreePath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
worktreeGitDirPath: "/path/to/repo/.git",
}, },
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
@ -106,8 +110,10 @@ branch refs/heads/mybranch-worktree
{ {
testName: "Worktree missing path", testName: "Worktree missing path",
repoPaths: &RepoPaths{ repoPaths: &RepoPaths{
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
worktreePath: "/path/to/repo", worktreePath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
worktreeGitDirPath: "/path/to/repo/.git",
}, },
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
@ -136,8 +142,10 @@ branch refs/heads/missingbranch
{ {
testName: "In linked worktree", testName: "In linked worktree",
repoPaths: &RepoPaths{ repoPaths: &RepoPaths{
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
worktreePath: "/path/to/repo-worktree", worktreePath: "/path/to/repo-worktree",
repoGitDirPath: "/path/to/repo/.git",
worktreeGitDirPath: "/path/to/repo/.git/worktrees/repo-worktree",
}, },
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
@ -184,11 +192,51 @@ branch refs/heads/mybranch-worktree
}, },
expectedErr: "", expectedErr: "",
}, },
{
testName: "In a submodule",
repoPaths: &RepoPaths{
repoPath: "/path/to/repo/mysubmodule",
worktreePath: "/path/to/repo/mysubmodule",
repoGitDirPath: "/path/to/repo/.git/modules/mysubmodule",
worktreeGitDirPath: "/path/to/repo/.git/modules/mysubmodule",
},
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
// A submodule's git dir doesn't live inside its working tree, and
// `git worktree list` reports the git dir rather than the working
// tree it belongs to.
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
`worktree /path/to/repo/.git/modules/mysubmodule
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
branch refs/heads/mybranch
`,
nil)
gitArgs := append(append([]string{"-C", "/path/to/repo/.git/modules/mysubmodule"}, getRevParseArgs()...), "--absolute-git-dir")
runner.ExpectGitArgs(gitArgs, "/path/to/repo/.git/modules/mysubmodule", nil)
_ = fs.MkdirAll("/path/to/repo/.git/modules/mysubmodule", 0o755)
},
expectedWorktrees: []*models.Worktree{
{
IsMain: true,
IsCurrent: true,
Path: "/path/to/repo/.git/modules/mysubmodule",
IsPathMissing: false,
GitDir: "/path/to/repo/.git/modules/mysubmodule",
Branch: "mybranch",
Head: "d85cc9d281fa6ae1665c68365fc70e75e82a042d",
Name: "mysubmodule",
},
},
expectedErr: "",
},
{ {
testName: "Detached HEAD worktree", testName: "Detached HEAD worktree",
repoPaths: &RepoPaths{ repoPaths: &RepoPaths{
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
worktreePath: "/path/to/repo", worktreePath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
worktreeGitDirPath: "/path/to/repo/.git",
}, },
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) { before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"}, runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},

View file

@ -16,11 +16,19 @@ type IGitConfig interface {
// this is for when you want to pass 'mykey' and check if the result is truthy // this is for when you want to pass 'mykey' and check if the result is truthy
GetBool(string) bool GetBool(string) bool
// SetDir pins the config commands to the given repo directory, so that
// they keep reading that repo's local config even if the process working
// directory changes later (i.e. the user switches repos while this
// instance is still in use by in-flight work). Called once, before the
// first read.
SetDir(string)
DropCache() DropCache()
} }
type CachedGitConfig struct { type CachedGitConfig struct {
cache map[string]string cache map[string]string
dir string
runGitConfigCmd func(*exec.Cmd) (string, error) runGitConfigCmd func(*exec.Cmd) (string, error)
log *logrus.Entry log *logrus.Entry
mutex sync.Mutex mutex sync.Mutex
@ -39,6 +47,13 @@ func NewCachedGitConfig(runGitConfigCmd func(*exec.Cmd) (string, error), log *lo
} }
} }
func (self *CachedGitConfig) SetDir(dir string) {
self.mutex.Lock()
defer self.mutex.Unlock()
self.dir = dir
}
func (self *CachedGitConfig) Get(key string) string { func (self *CachedGitConfig) Get(key string) string {
self.mutex.Lock() self.mutex.Lock()
defer self.mutex.Unlock() defer self.mutex.Unlock()
@ -69,6 +84,7 @@ func (self *CachedGitConfig) GetGeneral(args string) string {
func (self *CachedGitConfig) getGeneralAux(args string) string { func (self *CachedGitConfig) getGeneralAux(args string) string {
cmd := getGitConfigGeneralCmd(args) cmd := getGitConfigGeneralCmd(args)
cmd.Dir = self.dir
value, err := self.runGitConfigCmd(cmd) value, err := self.runGitConfigCmd(cmd)
if err != nil { if err != nil {
self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error()) self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error())
@ -79,6 +95,7 @@ func (self *CachedGitConfig) getGeneralAux(args string) string {
func (self *CachedGitConfig) getAux(key string) string { func (self *CachedGitConfig) getAux(key string) string {
cmd := getGitConfigCmd(key) cmd := getGitConfigCmd(key)
cmd.Dir = self.dir
value, err := self.runGitConfigCmd(cmd) value, err := self.runGitConfigCmd(cmd)
if err != nil { if err != nil {
self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error()) self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error())

View file

@ -116,3 +116,20 @@ func TestGet(t *testing.T) {
assert.Equal(t, "blah", result) assert.Equal(t, "blah", result)
assert.Equal(t, 1, count) assert.Equal(t, 1, count)
} }
// The config commands run in the directory set by SetDir rather than in the
// process's current directory: lazygit chdirs when switching repos, and config
// reads issued for the previous repo after that must keep addressing the repo
// they were created for.
func TestSetDirPinsCommandsToDirectory(t *testing.T) {
real := NewCachedGitConfig(
func(cmd *exec.Cmd) (string, error) {
assert.Equal(t, "/path/to/repo", cmd.Dir)
return "blah", nil
},
utils.NewDummyLog(),
)
real.SetDir("/path/to/repo")
real.Get("commit.gpgsign")
real.GetGeneral("--local --get-regexp foo")
}

View file

@ -28,5 +28,8 @@ func (self *FakeGitConfig) GetBool(key string) bool {
return isTruthy(self.Get(key)) return isTruthy(self.Get(key))
} }
func (self *FakeGitConfig) SetDir(dir string) {
}
func (self *FakeGitConfig) DropCache() { func (self *FakeGitConfig) DropCache() {
} }

View file

@ -160,3 +160,13 @@ func (c *Commit) IsTODO() bool {
func IsHeadCommit(commits []*Commit, index int) bool { func IsHeadCommit(commits []*Commit, index int) bool {
return !commits[index].IsTODO() && (index == 0 || commits[index-1].IsTODO()) return !commits[index].IsTODO() && (index == 0 || commits[index-1].IsTODO())
} }
func HeadCommitIdx(commits []*Commit) int {
for index, commit := range commits {
if !commit.IsTODO() {
return index
}
}
return -1
}

View file

@ -4,6 +4,9 @@ package models
type CommitFile struct { type CommitFile struct {
Path string Path string
// For a renamed file, the path it was renamed from; empty otherwise.
PreviousPath string
ChangeStatus string // e.g. 'A' for added or 'M' for modified. This is based on the result from git diff --name-status ChangeStatus string // e.g. 'A' for added or 'M' for modified. This is based on the result from git diff --name-status
} }
@ -23,6 +26,24 @@ func (f *CommitFile) Deleted() bool {
return f.ChangeStatus == "D" return f.ChangeStatus == "D"
} }
func (f *CommitFile) IsRename() bool {
return f.PreviousPath != ""
}
// Names returns an array containing just the path, or in the case of a rename,
// the after path and the before path.
func (f *CommitFile) Names() []string {
result := []string{f.Path}
if f.PreviousPath != "" {
result = append(result, f.PreviousPath)
}
return result
}
func (f *CommitFile) GetPath() string { func (f *CommitFile) GetPath() string {
return f.Path return f.Path
} }
func (f *CommitFile) GetPreviousPath() string {
return f.PreviousPath
}

View file

@ -0,0 +1,81 @@
package models
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stefanhaller/git-todo-parser/todo"
"github.com/stretchr/testify/assert"
)
func TestHeadCommitIdx(t *testing.T) {
testCases := []struct {
name string
commits []*Commit
expected int
}{
{
name: "first commit without rebase todos",
commits: makeTestCommits("a", "b"),
expected: 0,
},
{
name: "first non-todo commit during an interactive rebase",
commits: []*Commit{
makeTestTodoCommit(todo.Pick),
makeTestTodoCommit(todo.Reword),
makeTestCommit("a"),
makeTestCommit("b"),
},
expected: 2,
},
{
name: "no commits",
commits: nil,
expected: -1,
},
{
name: "only rebase todos",
commits: []*Commit{
makeTestTodoCommit(todo.Pick),
makeTestTodoCommit(todo.Reword),
},
expected: -1,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.expected, HeadCommitIdx(testCase.commits))
})
}
}
func TestIsHeadCommit(t *testing.T) {
commits := []*Commit{
makeTestTodoCommit(todo.Pick),
makeTestCommit("a"),
makeTestCommit("b"),
}
assert.False(t, IsHeadCommit(commits, 0))
assert.True(t, IsHeadCommit(commits, 1))
assert.False(t, IsHeadCommit(commits, 2))
}
func makeTestCommits(hashes ...string) []*Commit {
commits := make([]*Commit, 0, len(hashes))
for _, hash := range hashes {
commits = append(commits, makeTestCommit(hash))
}
return commits
}
func makeTestCommit(hash string) *Commit {
return NewCommit(&utils.StringPool{}, NewCommitOpts{Hash: hash})
}
func makeTestTodoCommit(action todo.TodoCommand) *Commit {
return NewCommit(&utils.StringPool{}, NewCommitOpts{Action: action})
}

View file

@ -18,10 +18,14 @@ type File struct {
Deleted bool Deleted bool
HasMergeConflicts bool HasMergeConflicts bool
HasInlineMergeConflicts bool HasInlineMergeConflicts bool
DisplayString string // How long the conflict markers in this file are, taken from its
ShortStatus string // e.g. 'AD', ' A', 'M ', '??' // conflict-marker-size gitattribute; 0 if it doesn't have that attribute. We
LinesDeleted int // only look this up for files that have inline merge conflicts.
LinesAdded int ConflictMarkerSize int
DisplayString string
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
LinesDeleted int
LinesAdded int
// If true, this must be a worktree folder // If true, this must be a worktree folder
IsWorktree bool IsWorktree bool

View file

@ -5,6 +5,7 @@ type GithubPullRequest struct {
Number int `json:"number"` Number int `json:"number"`
Title string `json:"title"` Title string `json:"title"`
State string `json:"state"` // "MERGED", "OPEN", "CLOSED", "DRAFT" State string `json:"state"` // "MERGED", "OPEN", "CLOSED", "DRAFT"
ChecksState string `json:"checksState"`
Url string `json:"url"` Url string `json:"url"`
HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"` HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"`
} }

View file

@ -91,6 +91,19 @@ func (self *CmdObj) AddEnvVars(vars ...string) *CmdObj {
return self return self
} }
// RemoveEnvVar removes every occurrence of the named environment variable from
// the command's environment. It's the counterpart to AddEnvVars, used to opt a
// single command out of a variable that the builder sets on every command by
// default.
func (self *CmdObj) RemoveEnvVar(name string) *CmdObj {
prefix := name + "="
self.cmd.Env = lo.Filter(self.cmd.Env, func(envVar string, _ int) bool {
return !strings.HasPrefix(envVar, prefix)
})
return self
}
func (self *CmdObj) GetEnvVars() []string { func (self *CmdObj) GetEnvVars() []string {
return self.cmd.Env return self.cmd.Env
} }
@ -144,7 +157,7 @@ func (self *CmdObj) ShouldStreamOutput() bool {
} }
// when you call this, then call Run(), we'll use a PTY to run the command. Only // when you call this, then call Run(), we'll use a PTY to run the command. Only
// has an effect if StreamOutput() was also called. Ignored on Windows. // has an effect if StreamOutput() was also called.
func (self *CmdObj) UsePty() *CmdObj { func (self *CmdObj) UsePty() *CmdObj {
self.usePty = true self.usePty = true

View file

@ -48,26 +48,34 @@ func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string
if len(shellFunctionsFile) > 0 { if len(shellFunctionsFile) > 0 {
commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr) commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr)
} }
quotedCommand := self.quotedCommandString(commandStr)
if self.platform.OS == "windows" {
return self.newWindowsShell(commandStr)
}
quotedCommand := self.Quote(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand)) cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs) return self.New(cmdArgs)
} }
func (self *CmdObjBuilder) quotedCommandString(commandStr string) string { // newWindowsShell wraps the command in `cmd.exe /s /c "<command>"`. The /s
// Windows does not seem to like quotes around the command // flag tells cmd to strip exactly the outermost pair of quotes and pass the
if self.platform.OS == "windows" { // rest through unchanged, which preserves any quoting the command itself
return strings.NewReplacer( // contains (e.g. `"C:\Program Files\my-editor.exe" file.txt`). Without /s,
"^", "^^", // cmd's default rules drop the wrong quotes once the command line contains
"&", "^&", // more than two of them.
"|", "^|", //
"<", "^<", // We bypass Go's standard arg quoting via SysProcAttr.CmdLine: it follows the
">", "^>", // CommandLineToArgvW convention (`\"` for inner quotes), but cmd.exe doesn't.
"%", "^%", func (self *CmdObjBuilder) newWindowsShell(commandStr string) *CmdObj {
).Replace(commandStr) args := []string{self.platform.Shell, "/s", self.platform.ShellArg, commandStr}
} cmdObj := self.New(args)
return self.Quote(commandStr) cmdLine := fmt.Sprintf(`%s /s %s "%s"`, self.platform.Shell, self.platform.ShellArg, commandStr)
setRawCmdLine(cmdObj.GetCmd(), cmdLine)
return cmdObj
} }
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder { func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {
@ -80,21 +88,47 @@ func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdO
} }
func (self *CmdObjBuilder) Quote(message string) string { func (self *CmdObjBuilder) Quote(message string) string {
var quote string
if self.platform.OS == "windows" { if self.platform.OS == "windows" {
quote = `\"` return quoteForWindows(message)
message = strings.NewReplacer(
`"`, `"'"'"`,
`\"`, `\\"`,
).Replace(message)
} else {
quote = `"`
message = strings.NewReplacer(
`\`, `\\`,
`"`, `\"`,
`$`, `\$`,
"`", "\\`",
).Replace(message)
} }
return quote + message + quote message = strings.NewReplacer(
`\`, `\\`,
`"`, `\"`,
`$`, `\$`,
"`", "\\`",
).Replace(message)
return `"` + message + `"`
}
// quoteForWindows encodes a value using the standard Windows command-line
// convention (the algorithm behind syscall.EscapeArg, reimplemented here so
// it's available on all platforms). The result is always wrapped in double
// quotes so cmd.exe and CommandLineToArgvW treat it as a single argument
// regardless of what shell metacharacters it contains.
func quoteForWindows(s string) string {
var b strings.Builder
b.WriteByte('"')
slashes := 0
for i := range len(s) {
c := s[i]
switch c {
case '\\':
slashes++
b.WriteByte(c)
case '"':
for ; slashes > 0; slashes-- {
b.WriteByte('\\')
}
b.WriteByte('\\')
b.WriteByte(c)
default:
slashes = 0
b.WriteByte(c)
}
}
for ; slashes > 0; slashes-- {
b.WriteByte('\\')
}
b.WriteByte('"')
return b.String()
} }

Some files were not shown because too many files have changed in this diff Show more