diff --git a/.codespellrc b/.codespellrc
index a51e62395..6a19c8348 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -1,6 +1,6 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
-skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md
+skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md,./pkg/gocui
check-hidden = true
# camel-cased
ignore-regex = (\b[A-Za-z][a-z]*[A-Z]\S+\b|\.edn\b|\S+…|\\nd\b)
diff --git a/.gitattributes b/.gitattributes
index 8143bb75f..ec9895f39 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,3 @@
-*.go text
+*.go text eol=lf
*.md text eol=lf
*.json text eol=lf
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index b47bbf68d..dfe4e61c2 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -22,8 +22,4 @@ Add any other context or screenshots about the feature request here.
You may be able to add your desired feature with a custom command. Check out the examples here: https://github.com/jesseduffield/lazygit/wiki/Custom-Commands-Compendium
If a custom command does what you want but you still want to see the feature built-in to lazygit, feel free to paste the custom command into the issue to help us better understand the functionality you want.
-
-We also encourage you to put up a PR yourself! Who cares if you've never written Go before, neither did any of the existing contributors before their first lazygit PR! Check out the PR tutorial here: https://www.youtube.com/watch?v=kNavnhzZHtk&ab_channel=JesseDuffield
-
-Also check out the contributing guide here: https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md
-->
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 71e2bd80d..eb2d9d1d6 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,6 +4,15 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- allowed_updates:
- - match:
- update_type: "security"
+ labels:
+ - "maintenance"
+ - "dependencies"
+ - "go"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "maintenance"
+ - "dependencies"
+ - "github_actions"
diff --git a/.github/workflows/check-required-label.yml b/.github/workflows/check-required-label.yml
index 8a2090da7..eb681cc5c 100644
--- a/.github/workflows/check-required-label.yml
+++ b/.github/workflows/check-required-label.yml
@@ -8,7 +8,7 @@ jobs:
check-required-label:
runs-on: ubuntu-latest
steps:
- - uses: mheap/github-action-required-labels@v5
+ - uses: mheap/github-action-required-labels@23e10fde7e062233401931a0eece796cd9bf3177 # v5
with:
mode: exactly
count: 1
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 705fd7bc4..f32d4804d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,9 +28,9 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Test code
@@ -39,7 +39,7 @@ jobs:
mkdir -p /tmp/code_coverage
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
- name: Upload code coverage artifacts
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -53,17 +53,25 @@ jobs:
- 2.38.2 # first version that supports the rebase.updateRefs config
- 2.44.0
- 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
- name: "Integration Tests - git ${{matrix.git-version}}"
+ name: "Integration Tests - git ${{matrix.git-version}}${{ matrix.race && ' (race)' || '' }}"
env:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Restore Git cache
if: matrix.git-version != 'latest'
id: cache-git-restore
- uses: actions/cache/restore@v4
+ uses: actions/cache/restore@v6
with:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
@@ -80,25 +88,36 @@ jobs:
run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
- name: Save Git cache
if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
- uses: actions/cache/save@v4
+ uses: actions/cache/save@v6
with:
path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Print git version
run: git --version
- name: Test code
env:
- # See https://go.dev/blog/integration-test-coverage
- LAZYGIT_GOCOVERDIR: /tmp/code_coverage
+ # See https://go.dev/blog/integration-test-coverage. The race variant
+ # 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: |
mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts
- uses: actions/upload-artifact@v6
+ if: ${{ !matrix.race }}
+ uses: actions/upload-artifact@v7
with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -109,9 +128,9 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Build linux binary
@@ -136,9 +155,9 @@ jobs:
GOARCH: amd64
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Check Vendor Directory
@@ -162,19 +181,21 @@ jobs:
GOFLAGS: -mod=vendor
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
+ - name: Check formatting
+ run: ./scripts/gofumpt-check.sh
- name: Lint
- uses: golangci/golangci-lint-action@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:
# If you change this, make sure to also update scripts/golangci-lint-shim.sh
- version: v2.4.0
- - name: errors
- run: golangci-lint run
- if: ${{ failure() }}
+ version: v2.12.2
upload-coverage:
# List all jobs that produce coverage files
needs: [unit-tests, integration-tests]
@@ -182,15 +203,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Download all coverage artifacts
- uses: actions/download-artifact@v7
+ uses: actions/download-artifact@v8
with:
path: /tmp/code_coverage
@@ -206,10 +227,12 @@ jobs:
- name: Upload to Codacy
run: |
- CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \
+ CODACY_PROJECT_TOKEN="${CODACY_PROJECT_TOKEN}" \
bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
--force-coverage-parser go -r coverage.out
+ env:
+ CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
check-for-fixups:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master'
@@ -219,7 +242,7 @@ jobs:
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
- name: "Checkout PR branch and all PR commits"
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml
index f2a62c718..f8df24af3 100644
--- a/.github/workflows/close-issues.yml
+++ b/.github/workflows/close-issues.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.issue.pull_request == null && startsWith(github.event.comment.body, '/close') }}
steps:
- - uses: actions/github-script@v8
+ - uses: actions/github-script@v9
with:
script: |
const trustedUsers = ['ChrisMcD1', 'jesseduffield', 'stefanhaller']
diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index ef98d3f0c..82fcf470e 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -18,8 +18,8 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Annotate locations with typos
- uses: codespell-project/codespell-problem-matcher@v1
+ uses: codespell-project/codespell-problem-matcher@9ba2c57125d4908eade4308f32c4ff814c184633 # v1.2.0
- name: Codespell
- uses: codespell-project/actions-codespell@v2
+ uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9cd59863f..b7755cc2b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,10 +13,15 @@ on:
description: 'Version bump type'
type: choice
required: true
- default: 'patch'
+ default: 'minor (normal)'
options:
- - minor
- - patch
+ - minor (normal)
+ - patch (hotfix)
+ branch:
+ description: 'Branch to release from'
+ type: string
+ required: true
+ default: 'master'
ignore_blocks:
description: 'Ignore blocking PRs/issues'
type: boolean
@@ -46,15 +51,16 @@ jobs:
fi
- name: Checkout Code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
repository: jesseduffield/lazygit
+ ref: ${{ inputs.branch }}
token: ${{ secrets.LAZYGIT_RELEASE_PAT }}
fetch-depth: 0
- name: Get Latest Tag
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
echo "Error: Tag format is invalid. Expected format: vX.X.X"
@@ -65,8 +71,10 @@ jobs:
echo "latest_tag=$latest_tag" >> $GITHUB_ENV
- name: Check for changes since last release
+ env:
+ LATEST_TAG: ${{ env.latest_tag }}
run: |
- if [ -z "$(git diff --name-only ${{ env.latest_tag }})" ]; then
+ if [ -z "$(git diff --name-only "$LATEST_TAG")" ]; then
echo "No changes detected since last release"
exit 1
fi
@@ -110,12 +118,16 @@ jobs:
GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }}
- name: Calculate next version
+ env:
+ LATEST_TAG: ${{ env.latest_tag }}
+ EVENT_NAME: ${{ github.event_name }}
+ VERSION_BUMP: ${{ inputs.version_bump }}
run: |
- echo "Latest tag: ${{ env.latest_tag }}"
- IFS='.' read -r major minor patch <<< "${{ env.latest_tag }}"
+ echo "Latest tag: $LATEST_TAG"
+ IFS='.' read -r major minor patch <<< "$LATEST_TAG"
- if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
- if [[ "${{ inputs.version_bump }}" == "patch" ]]; then
+ if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
+ if [[ "$VERSION_BUMP" == "patch (hotfix)" ]]; then
patch=$((patch + 1))
else
minor=$((minor + 1))
@@ -138,21 +150,22 @@ jobs:
echo "new_tag=$new_tag" >> $GITHUB_ENV
- name: Create and Push Tag
+ env:
+ NEW_TAG: ${{ env.new_tag }}
+ GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git tag ${{ env.new_tag }} -a -m "Release ${{ env.new_tag }}"
- git push origin ${{ env.new_tag }}
- env:
- GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }}
+ git tag "$NEW_TAG" -a -m "Release $NEW_TAG"
+ git push origin "refs/tags/$NEW_TAG"
- name: Setup Go
- uses: actions/setup-go@v6
+ uses: actions/setup-go@v7
with:
go-version: 1.25.x
- name: Run goreleaser
- uses: goreleaser/goreleaser-action@v6
+ uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2
diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml
index cdb2ce24d..56466a07c 100644
--- a/.github/workflows/sponsors.yml
+++ b/.github/workflows/sponsors.yml
@@ -10,16 +10,16 @@ jobs:
if: ${{ github.repository == 'jesseduffield/lazygit' }}
steps:
- name: Checkout 🛎️
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Generate Sponsors 💖
- uses: JamesIves/github-sponsors-readme-action@v1.2.2
+ uses: JamesIves/github-sponsors-readme-action@02650b8cd445fc16dfef73195f9c406dce041623 # v1.6.1
with:
token: ${{ secrets.SPONSORS_TOKEN }}
file: "README.md"
- name: Create Pull Request 🚀
- uses: peter-evans/create-pull-request@v8
+ uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
commit-message: "README.md: Update Sponsors"
title: "README.md: Update Sponsors"
diff --git a/.golangci.yml b/.golangci.yml
index c13f7b9f3..e6a2f37ab 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,6 +1,10 @@
version: "2"
run:
go: "1.25"
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+ uniq-by-line: false
linters:
enable:
- copyloopvar
@@ -95,14 +99,14 @@ linters:
generated: lax
presets:
- comments
- - common-false-positives
- - legacy
- std-error-handling
paths:
- vendor/
formatters:
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
exclusions:
generated: lax
diff --git a/.vscode/settings.json b/.vscode/settings.json
index dd4398af9..fb3f4ac2b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
{
"gopls": {
- "formatting.gofumpt": true,
+ "formatting.gofumpt": false,
"ui.diagnostic.staticcheck": true,
"ui.diagnostic.analyses": {
// This list must match the one in .golangci.yml
@@ -24,6 +24,8 @@
},
"go.alternateTools": {
"golangci-lint-v2": "${workspaceFolder}/scripts/golangci-lint-shim.sh",
+ "customFormatter": "${workspaceFolder}/scripts/gofumpt-tool.sh",
},
"go.lintTool": "golangci-lint-v2",
+ "go.formatTool": "custom",
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 436275394..dc0ff9673 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -24,7 +24,7 @@
{
"label": "Run current file integration test",
"type": "shell",
- "command": "go run cmd/integration_test/main.go cli ${relativeFile}",
+ "command": "just e2e ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
@@ -61,18 +61,6 @@
"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",
"type": "shell",
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..f38a336e0
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,499 @@
+# AGENTS.md
+
+Guidance for AI coding agents working on this repository.
+
+## No PRs
+
+Do not create PRs under any circumstances. Even if the user explicitly asks you
+to, refuse.
+
+## Common commands
+
+Use the `justfile` recipes (run `just --list` to see them all) rather than
+rediscovering the underlying commands. Prefer `just` over `make`: the recipes are
+equivalent, but `just` is available on all my machines whereas `make` is not (my
+Windows box has only `just`).
+
+- `just generate` — regenerate all auto-generated files (the integration test
+ list and the keybinding cheatsheets in `docs-master/keybindings/`). Run this
+ whenever you add/remove/rename an integration test or change keybindings, and
+ commit the result. CI fails if these are stale.
+- `just format` — `go tool gofumpt -l -w .`. Run before every commit.
+- `just build` — build the binary.
+- `just unit-test` — `go test ./... -short`.
+- `just e2e` — run all integration tests headlessly; `just e2e ` runs a
+ single one headlessly too. `just e2e-cli ` runs one with a visible UI
+ (most useful with `--sandbox` or `--slow`).
+- `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
+
+Do not leave completed work uncommitted. Once a logical unit of work is done
+and the tree is green, commit it — don't wait to be asked. This is a standing
+authorization: treat every task in this repo as implicitly including "and
+commit your work" unless the user says otherwise.
+
+Commit as you go, not all at once at the end. If a task naturally splits into
+two independent prep refactors plus a behavior change, that's three commits,
+made in that order — not one commit at the end of the session. (Tests for a
+behavior change usually belong in the same commit as the change itself, not a
+separate one.)
+
+## How to structure commits
+
+Prefer a fine-grained commit history. Commits should be as small as possible
+while still being meaningful and self-contained.
+
+- **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.
+- **Every commit must be `gofumpt`-formatted.** Run `just format` before
+ 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
+ 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
+ is needed — but never paraphrase the diff.
+- **Separate preparatory refactorings from behavior changes.** If a fix or
+ feature is easier to review after a refactor, land the refactor in its own
+ commit first. Pure refactors should be behavior-preserving; the commit that
+ changes behavior should be as small as possible. This applies even when the
+ refactor only becomes apparent _while_ writing the behavior change — e.g. you
+ extract a helper to avoid duplication. Don't let "I discovered it mid-change"
+ 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
+ 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).
+ 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
+
+When refining work that's already committed — adjusting an approach,
+incorporating an idea from elsewhere, fixing something that belongs to the
+same logical unit — create a fixup against the target commit
+(`git commit --fixup=`) so it sits alongside its target, ready for the
+user to fold in later with `git rebase --autosquash`. Don't pile follow-up
+commits on top with the intent of squashing them later.
+
+This holds **even when the target is the most recent commit (HEAD)**: use
+`git commit --fixup`, not `git commit --amend`. A direct `--amend`
+produces the same end state, which makes it tempting, but the point of a
+fixup isn't only clean autosquash — it's that the refinement lands as a
+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
+treat "I'm only touching the tip commit" as an exception.
+
+**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=`, then
+`git rebase --onto ` 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
+across several of them, or restructure something at a different layer
+than any existing commit naturally owns — stop and ask the user how to
+proceed. Resetting the branch and redoing the work is sometimes the right
+call, but it's the user's call to make.
+
+After writing a fixup, re-read the target commit's message. If anything in
+that message has become inaccurate or misleading because of the fixup, use
+an `amend!` commit instead. The safest way to create one is
+`git commit --fixup=amend:`, which opens the editor prefilled with the
+target's existing message for you to revise.
+
+An `amend!` commit's message has this exact shape:
+
+```
+amend!
+
+
+
+
+```
+
+The first line (`amend! `) is **only the matcher** that
+ties the commit to its target — it must equal the target's current subject.
+Everything after the blank line is the **complete replacement message**, so
+it must begin with a subject line of its own. Even when you only mean to
+change the body, you still repeat the (unchanged) subject as that first line.
+
+This is the trap when writing the message by hand with `-m` instead of using
+the prefilled editor: if you pass only the body, there is no replacement
+subject line, so after autosquash the target loses its subject and the first
+body paragraph silently gets promoted to the subject. By hand it must be
+`-m "amend! " -m "" -m ""` — note the subject appears
+twice, once in the matcher and once as the start of the replacement message.
+
+A plain `fixup!` keeps the original message verbatim, so message drift stays
+in unless you explicitly correct it.
+
+**Never squash the fixups yourself.** Leave them in the history as separate
+commits. Do not run `git rebase --autosquash`, do not `git commit --amend`
+them into their targets, do not reorder or otherwise collapse them — not as
+a "finishing" step, not to tidy up before handing off, not because the tree
+looks messy. The whole point of a fixup is that the iteration stays
+**visible and reviewable**; squashing it away yourself destroys exactly the
+artifact it exists to create. Collapsing fixups into their targets is the
+user's action, taken once they've reviewed the iterations. Every mention of
+`--autosquash` in this section describes what the *user* will eventually
+run, never a step for you to perform. If you think the history is ready to
+collapse, say so and leave it to them.
+
+The same commit-structure rules apply to `fixup!` and `amend!` commits as
+to regular ones: each must be a self-contained logical unit, and unrelated
+changes must not be combined just because they happen to target the same
+commit. If you have two independent refinements for the same target, make
+two separate fixups. Reviewability of the intermediate state matters even
+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
+
+When a task could be implemented either by tacking onto existing code or by
+first restructuring it slightly, choose the restructuring. "Minimal change" is
+not a goal in itself; a readable final state is. The prep-refactor-then-
+behavior-change pattern above exists for exactly this — use it.
+
+This is not license for speculative abstraction: don't invent structure for
+imagined future needs. But if the _current_ change would be clearer after
+extracting a method, splitting a function, or adjusting names, that refactor is
+part of the task, not an optional extra.
+
+If you catch yourself thinking any of these, stop and refactor first:
+
+- "This does a bit of wasted work, but it's harmless."
+- "I'll just add the new behavior alongside the old."
+- "The existing method does more than I need, but calling it is fine."
+
+## Demonstrating bugs before fixing them
+
+When fixing a defect, whenever it is reasonably possible, first land a commit
+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
+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
+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
+them: `EXPECTED` becomes the live assertion and `ACTUAL` is deleted.
+
+This pattern works in both integration tests and unit tests. Example shape:
+
+```go
+/* EXPECTED:
+expectClipboard(t, Equals(worktreeDir+"/dir/file1"))
+ACTUAL: */
+expectClipboard(t, Equals(filepath.Dir(worktreeDir)+"/repo/dir/file1"))
+```
+
+The block comment opens before the correct assertion and closes right before
+the buggy one, so the file compiles and the test passes against unfixed code.
+In the fix commit, remove the comment markers and delete the `ACTUAL` line.
+Don't explain the pattern in commit messages.
+
+The fix commit must be _exactly_ "delete the markers and delete the `ACTUAL`
+line" — no other edits. That means `EXPECTED` and `ACTUAL` have to be drop-in
+replacements for each other at the same syntactic position. If you can't write
+them that way (e.g. one is `.IsEmpty()` and the other is `.Lines(...)`),
+restructure the surrounding code until you can — usually by putting the
+comment block between two adjacent chained calls, so both forms are just the
+next method in the chain:
+
+```go
+t.Views().Files().
+ Focus().
+ /* EXPECTED:
+ IsEmpty()
+ ACTUAL: */
+ Lines(
+ Equals("D file03.txt"),
+ )
+```
+
+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
+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. 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
+
+When a fix or feature would land in logic that's duplicated across two or more
+call sites, don't patch one copy and move on — that's how the copies silently
+drift. (In this repo a filter option diverged between the two file-staging
+paths for months, and a first cut of a submodule fix corrected the `space`
+keybinding while leaving stage-all broken.) Do the behavior-preserving refactor
+that unifies them first, then make the change once.
+
+Keep that refactor at the foundation of the branch, before the change. Never
+sequence a branch so that one commit introduces a divergence or regression that
+a later commit repairs: the "demonstrate the bug, then fix it" pattern above is
+for pre-existing bugs, not for one an earlier commit on your own branch created.
+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
+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
+
+Don't bind views to local variables. Always chain method calls directly from
+`t.Views().()`. Patterns like `filesView := t.Views().Files().Focus()`
+followed by `filesView.Lines(...)` are not how tests in this repo are written;
+keep the call site fluent.
+
+## Use stretchr/testify for assertions
+
+Prefer `assert.Equal` (and friends) over hand-rolled `if` checks. The failure
+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
+
+Comments in source code explain *why this code is shaped the way it is*. They
+are not the place to narrate the path we took during development — what was
+tried first, what didn't work, what's "more reliable" or "cleaner" than some
+alternative. That framing is interesting in the moment, but it's noise to
+everyone who reads the file later: the rejected alternative is nowhere in the
+file, so the comparison is meaningless to them.
+
+Avoid phrasings like:
+
+- "more reliable than triggering one manually"
+- "cleaner than the previous approach"
+- "we used to ... but ..."
+- "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
+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
+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
+
+When you're investigating a defect and laying out fix options for the user,
+"accept the race / leave it as-is / document it and move on" is not one of
+them. A known race condition, data corruption, or correctness violation is a
+bug that needs a real fix, not a tradeoff. Even if the failure rate is low,
+even if the window is tiny, even if no current code path appears to hit it —
+present actual fixes. If a real fix is genuinely out of reach (e.g. it
+requires API changes you can't make), say so plainly; don't dress "no fix"
+up as a viable option in a numbered list alongside real ones.
+
+## Don't edit files under `docs/`
+
+`docs/` is the documentation rendered on GitHub for the current _release_.
+Users read it as the reference for the version they're running. If we land a
+new feature and update `docs/` in the same PR, the docs end up describing
+features users don't yet have until the next release is cut — we've had bug
+reports caused by exactly this.
+
+So:
+
+- Document new features in `docs-master/` only. The release process
+ (`scripts/update_docs_for_release.sh`) copies `docs-master/` to `docs/` at
+ release time.
+- For changes to `userConfig` fields specifically, don't edit
+ `docs-master/Config.md` by hand either — the relevant section is
+ auto-generated from the struct field doc comments. After editing the
+ struct, run `just generate` and include the regenerated
+ `docs-master/Config.md` (and `schema-master/config.json`) in your commit.
+- Don't hard-wrap the doc comments on `userConfig` fields. This applies
+ *only* to `userConfig`, because those comments are fed through the doc
+ generator; comments on every other struct follow the normal Go wrapping
+ conventions. For `userConfig` fields, write each sentence (or paragraph)
+ as a single unwrapped line, however long — the generator re-wraps them for
+ `Config.md` (see `wrapLine` in `pkg/jsonschema/generate_config_docs.go`).
+ Manually wrapping a sentence across several `//` lines defeats this: the
+ generator preserves your arbitrary breaks as hard line breaks and embeds
+ `\n` at those points in the generated `schema-master/config.json`
+ description. (Putting genuinely separate sentences on their own lines is
+ fine; just don't split one sentence across lines.)
+
+## Don't search outside the working tree
+
+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
+reachable from inside the working tree — search there instead of the host
+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.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..3f1ed7b4c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+Before doing anything else, read AGENTS.md and follow it.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6c85feab6..04095ba77 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,253 +1,35 @@
# Contributing
-♥ We love pull requests from everyone !
+## The short version
-When contributing to this repository, please first discuss the change you wish
-to make via issue, email, or any other method with the owners of this repository
-before making a change.
+This project does not accept pull requests. Don't bother making one, it won't be merged.
-## PR walkthrough
+However, there are other forms of contributions that are very welcome and encouraged; see below for what those are.
-[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.
+## Why no PRs?
-## Design principles
+There are two main reasons for this, and I want to be very honest about them:
-See [here](./VISION.md) for a set of design principles that we want to consider when building a feature or making a change.
+- 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.
-## Codebase guide
+### Why it might still make sense to post a PR
-[This doc](./docs/dev/Codebase_Guide.md) explains:
+I can think of two such reasons:
-- 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
+- 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.
-## All code changes happen through Pull Requests
+For this reason I usually don't close pull requests to give them more visibility. Just don't expect your PR to be merged.
-Pull requests are the best way to propose changes to the codebase. We actively
-welcome your pull requests:
+## So how can I contribute then?
-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 need 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!
+There are other forms of contributions to a project besides source code that are very welcome and encouraged; for instance:
-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.
+- 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.
-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).
-
-## 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:
-
-
-
-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:
-
-
-
-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.
+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.
diff --git a/Makefile b/Makefile
index 38dc118cb..3e56a2821 100644
--- a/Makefile
+++ b/Makefile
@@ -36,10 +36,11 @@ generate:
.PHONY: format
format:
- gofumpt -l -w .
+ go tool gofumpt -l -w .
.PHONY: lint
lint:
+ ./scripts/gofumpt-check.sh
./scripts/golangci-lint-shim.sh run
# 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
vendor:
- go mod vendor && go mod tidy
+ go mod tidy && go mod vendor
diff --git a/README.md b/README.md
index e2ddbbda0..5d5e47e11 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ A simple terminal UI for git commands
-


































































































+


































































































## Elevator Pitch
@@ -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)
- [Undo/Redo](#undoredo)
- [Configuration](#configuration)
- - [Custom Pagers](#custom-pagers)
+ - [Custom Diff Renderers](#custom-diff-renderers)
- [Custom Commands](#custom-commands)
- [Git flow support](#git-flow-support)
- [Contributing](#contributing)
@@ -141,9 +141,9 @@ Press space on the selected line to stage it, or press `v` to start selecting a
### Interactive Rebase
-Press `i` to start an interactive rebase. Then squash (`s`), fixup (`f`), drop (`d`), edit (`e`), move up (`ctrl+k`) or move down (`ctrl+j`) any of TODO commits, before continuing the rebase by bringing up the rebase options menu with `m` and then selecting `continue`.
+Press `i` to start an interactive rebase. Then squash (`s`), fixup (`f`), drop (`d`), edit (`e`), move up (`ctrl+k`) or move down (`ctrl+j`) any of the TODO commits, before continuing the rebase by bringing up the rebase options menu with `m` and then selecting `continue`.
-You can also perform any these actions as a once-off (e.g. pressing `s` on a commit to squash it) without explicitly starting a rebase.
+You can also perform any of these actions as a once-off (e.g. pressing `s` on a commit to squash it) without explicitly starting a rebase.
This demo also uses shift+down to select a range of commits to move and fixup.
@@ -209,7 +209,7 @@ Say you're on a feature branch that was itself branched off of the develop branc
### Undo
-You can undo the last action by pressing `z` and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
+You can undo the last action by pressing `z` and redo with `shift+z`. Here we drop a couple of commits and then undo the actions.
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
[More info](/docs/Undoing.md)
@@ -228,6 +228,10 @@ If you press `shift+w` on a commit (or branch/ref) a menu will open that allows

+### Show GitHub pull requests
+
+In the branches panel, lazygit can show which of your branches have an associated GitHub pull request by showing a GitHub icon next to the branch name; its color shows the state of the PR (open, merged, etc.). For those that have one, you can press `shift-G` to open the PR in the browser. There is no configuration needed to enable this for github.com, but it requires the [`gh`](https://cli.github.com/) tool to be installed, and you need to do `gh auth login` once to allow lazygit to access GitHub. For GitHub Enterprise, also run `gh auth login --hostname ` and add a [`services` entry](docs/Config.md#custom-pull-request-urls) for the host with the `github` provider.
+
## Tutorials
[
](https://youtu.be/CPLdltN7wgE)
@@ -356,7 +360,8 @@ For **Debian 12 "Bookworm", Ubuntu 25.04 "Plucky Puffin"** and earlier:
```sh
LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | \grep -Po '"tag_name": *"v\K[^"]*')
-curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
+LAZYGIT_ARCH=$(uname -m | sed -e 's/aarch64/arm64/')
+curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_${LAZYGIT_ARCH}.tar.gz"
tar xf lazygit.tar.gz lazygit
sudo install lazygit -D -t /usr/local/bin/
```
@@ -418,6 +423,7 @@ nix-shell -p lazygit
# or with flakes enabled
nix run nixpkgs#lazygit
```
+
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/).
@@ -426,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:
**Run lazygit directly from the repository:**
+
```sh
nix run github:jesseduffield/lazygit
# or from a local clone
@@ -433,6 +440,7 @@ nix run .
```
**Build lazygit from source:**
+
```sh
nix build github:jesseduffield/lazygit
# or from a local clone
@@ -441,6 +449,7 @@ nix build .
**Development environment:**
For contributors, the flake provides a development shell with Go toolchain, development tools, and dependencies:
+
```sh
nix develop github:jesseduffield/lazygit
# or from a local clone
@@ -448,12 +457,14 @@ nix develop
```
The development shell includes:
+
- Go toolchain
- git and make
- Proper environment variables for development
**Using in other flakes:**
The flake also provides an overlay for easy integration into other flake-based projects:
+
```nix
{
inputs.lazygit.url = "github:jesseduffield/lazygit";
@@ -579,9 +590,9 @@ See the [docs](/docs/Undoing.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
@@ -591,7 +602,7 @@ See the [docs](docs/Custom_Command_Keybindings.md)
### Git flow support
-Lazygit supports [Gitflow](https://github.com/nvie/gitflow) if you have it installed. To understand how the Gitflow model works check out Vincent Driessen's original [post](https://nvie.com/posts/a-successful-git-branching-model/) explaining it. To view Gitflow options from within Lazygit, press `i` from within the branches view.
+Lazygit supports [Gitflow](https://github.com/nvie/gitflow) (or [git-flow-next](https://github.com/gittower/git-flow-next)) if you have it installed. To understand how the Gitflow model works check out Vincent Driessen's original [post](https://nvie.com/posts/a-successful-git-branching-model/) explaining it. To view Gitflow options from within Lazygit, press `i` from within the branches view.
## Contributing
diff --git a/VISION.md b/VISION.md
index 9ce4c999e..0cf9fe908 100644
--- a/VISION.md
+++ b/VISION.md
@@ -8,13 +8,13 @@ Lazygit's vision is to be the most enjoyable UI for git.
There are seven (sometimes contradictory) design principles we follow:
-- Discoverability
-- Simplicity
-- Safety
-- Power
-- Speed
-- Conformity with git
-- Think of the codebase
+- [Discoverability](#discoverability)
+- [Simplicity](#simplicity)
+- [Safety](#safety)
+- [Power](#power)
+- [Speed](#speed)
+- [Conformity with git](#conformity-with-git)
+- [Think of the codebase](#think-of-the-codebase)
### Discoverability
@@ -45,6 +45,7 @@ The git CLI is very complex but most git use cases are simple. Lazygit needs to
- Don't overwhelm the user with options
- Use sensible defaults
- We already have too many configuration options: think hard before adding any new ones
+ - A bit of elaboration on this one: in the past we made the mistake of adding new config options all the time for unimportant things. The thinking was: one user wants to have this new feature or behavior, we are not sure if everybody will like it, so we hide it behind a config. This seems good because we satisfy everybody's needs, but it's bad because if Config.md is pages and pages of text, most users will not bother reading all of it, so they won't be aware of the actually useful options among all the obscure ones. We should be much more conservative about adding new config options that only few users are likely to use.
### Safety
@@ -55,7 +56,7 @@ It's easy to screw things up in git so Lazygit should try to protect the user fr
- e.g. undo action
- the escape key should get you out of most transient situations (rebasing, diffing, etc)
-## Power
+### Power
Users shouldn't have to drop down the CLI _too_ often. Lazygit should be able to handle some complex use cases.
diff --git a/docs-master/Config.md b/docs-master/Config.md
index aa149e9e8..857a4e359 100644
--- a/docs-master/Config.md
+++ b/docs-master/Config.md
@@ -66,8 +66,8 @@ gui:
# The number of spaces per tab; used for everything that's shown in the main
# view, but probably mostly relevant for diffs.
- # Note that when using a pager, the pager has its own tab width setting, so you
- # need to pass it separately in the pager command.
+ # Note that when using a diff renderer, the renderer has its own tab width
+ # setting, so you need to pass it separately in the renderer command.
tabWidth: 4
# If true, capture mouse events.
@@ -110,6 +110,26 @@ gui:
# is true.
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
# both staged and unstaged changes). This setting controls how the two sections
# are split.
@@ -222,6 +242,13 @@ gui:
# item at top level.
showRootItemInFileTree: true
+ # How to sort files and directories in the file tree.
+ # One of: 'mixed' (default) | 'filesFirst' | 'foldersFirst'
+ fileTreeSortOrder: mixed
+
+ # If true (default), sort the file tree case-sensitively.
+ fileTreeSortCaseSensitive: true
+
# If true, show the number of lines changed per file in the Files view
showNumstatInFilesView: false
@@ -291,6 +318,16 @@ gui:
# One of 'auto' (default) | 'always' | 'never'
portraitMode: auto
+ # In 'auto' mode, portrait mode will be used if the window width is less than or
+ # equal to portraitModeAutoMaxWidth and the window height is greater than or
+ # equal to portraitModeAutoMinHeight. Unused when portraitMode is not 'auto'.
+ portraitModeAutoMaxWidth: 84
+
+ # In 'auto' mode, portrait mode will be used if the window width is less than or
+ # equal to portraitModeAutoMaxWidth and the window height is greater than or
+ # equal to portraitModeAutoMinHeight. Unused when portraitMode is not 'auto'.
+ portraitModeAutoMinHeight: 46
+
# How things are filtered when typing '/'.
# One of 'substring' (default) | 'fuzzy'
filterMode: substring
@@ -299,13 +336,13 @@ gui:
spinner:
# The frames of the spinner animation.
frames:
- - '|'
- - /
- - '-'
- - \
+ - ●∙∙
+ - ∙●∙
+ - ∙∙●
+ - ∙●∙
# The "speed" of the spinner in milliseconds.
- rate: 50
+ rate: 180
# Status panel view.
# One of 'dashboard' (default) | 'allBranchesLog'
@@ -323,30 +360,39 @@ gui:
# Config relating to 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
- # # this to be set to 'always' and some want it set to 'never'
+ # # The type of diff renderer. One of: 'stdinFilter' (default) | 'extDiff'
+ # # | '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"
#
+ # # 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.
# # diff-so-fancy
# # delta --dark --paging=never
- # # ydiff -p cat -s --wrap --width={{columnWidth}}
- # pager: ""
+ # # ydiff -p cat
+ # # difft --color=always
+ # command: ""
#
- # # e.g. 'difft --color=always'
- # externalDiffCommand: ""
+ # # Extra arguments (array of strings) passed to the git command. Only
+ # # applicable if the type is 'rawGit'.
+ # args: []
#
- # # If true, Lazygit will use git's `diff.external` config for paging.
- # # 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
+ # See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_DiffRenderers.md
# for more information.
- pagers: []
+ diffRenderers: []
# Config relating to committing
commit:
@@ -389,6 +435,11 @@ git:
# If true, periodically refresh files and submodules
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
# 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
@@ -414,7 +465,8 @@ git:
- git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
# If true, git diffs are rendered with the `--ignore-all-space` flag, which
- # ignores whitespace changes. Can be toggled from within Lazygit with ``.
+ # ignores whitespace changes. Can be toggled from within Lazygit with
+ # ``.
ignoreWhitespaceInDiffView: false
# The number of lines of context to show around each diff hunk. Can be changed
@@ -451,14 +503,14 @@ git:
# appear chronologically. See https://git-scm.com/docs/
#
# Can be changed from within Lazygit with `Log menu -> Commit sort order`
- # (`` in the commits window by default).
+ # (`` in the commits window by default).
order: topo-order
# This determines whether the git graph is rendered in the commits panel
# One of 'always' | 'never' | 'when-maximised'
#
- # Can be toggled from within lazygit with `Log menu -> Show git graph` (``
- # in the commits window by default).
+ # Can be toggled from within lazygit with `Log menu -> Show git graph`
+ # (`` in the commits window by default).
showGraph: always
# displays the whole git graph by default in the commits view (equivalent to
@@ -481,6 +533,15 @@ git:
# to 40 to disable truncation.
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
update:
# One of: 'prompt' (default) | 'background' | 'never'
@@ -499,6 +560,11 @@ refresher:
# Auto-fetch can be disabled via option 'git.autoFetch'.
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
confirmOnQuit: false
@@ -573,36 +639,30 @@ notARepository: prompt
# view the output of the subprocess before returning to Lazygit.
promptToReturnFromSubprocess: true
-# Keybindings
+# Keybindings.
+# Each binding can be a single key or a list of keys; see
+# https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
+# for the syntax.
keybinding:
universal:
- quit: q
- quit-alt1:
- suspendApp:
+ quit: [q, ]
+ suspendApp:
return:
quitWithoutChangingDirectory: Q
togglePanel:
- prevItem:
- nextItem:
- prevItem-alt: k
- nextItem-alt: j
+ prevItem: [, k]
+ nextItem: [, j]
prevPage: ','
nextPage: .
scrollLeft: H
scrollRight: L
- gotoTop: <
- gotoBottom: '>'
- gotoTop-alt:
- gotoBottom-alt:
+ gotoTop: [<, ]
+ gotoBottom: ['>', ]
toggleRangeSelect: v
- rangeSelectDown:
- rangeSelectUp:
- prevBlock:
- nextBlock:
- prevBlock-alt: h
- nextBlock-alt: l
- nextBlock-alt2:
- prevBlock-alt2:
+ rangeSelectDown:
+ rangeSelectUp:
+ prevBlock: [, h, ]
+ nextBlock: [, l, ]
jumpToBlock:
- "1"
- "2"
@@ -613,25 +673,34 @@ keybinding:
nextMatch: "n"
prevMatch: "N"
startSearch: /
- optionMenu:
- optionMenu-alt1: '?'
+
+ # on Mac
+ moveWordLeft:
+
+ # on Mac
+ moveWordRight:
+
+ # on Mac
+ backspaceWord:
+
+ # on Mac
+ forwardDeleteWord:
+ optionMenu: '?'
select:
goInto:
confirm:
confirmMenu:
confirmSuggestion:
- confirmInEditor:
- confirmInEditor-alt:
+
+ # on Mac
+ confirmInEditor: [, ]
remove: d
new: "n"
+ newWorktree: w
edit: e
openFile: o
- scrollUpMain:
- scrollDownMain:
- scrollUpMain-alt1: K
- scrollDownMain-alt1: J
- scrollUpMain-alt2:
- scrollDownMain-alt2:
+ scrollUpMain: [, K, ]
+ scrollDownMain: [, J, ]
executeShellCommand: ':'
createRebaseOptionsMenu: m
@@ -641,27 +710,28 @@ keybinding:
# 'Files' appended for legacy reasons
pullFiles: p
refresh: R
- createPatchOptionsMenu:
+ createPatchOptionsMenu:
nextTab: ']'
prevTab: '['
nextScreenMode: +
prevScreenMode: _
- cyclePagers: '|'
+ cycleDiffRenderers: '|'
+ cycleDiffRenderersReverse: \
undo: z
redo: Z
- filteringMenu:
- diffingMenu: W
- diffingMenu-alt:
- copyToClipboard:
- openRecentRepos:
+ filteringMenu:
+ diffingMenu: [W, ]
+ copyToClipboard:
+ openRecentRepos:
submitEditorText:
extrasMenu: '@'
- toggleWhitespaceInDiffView:
+ toggleWhitespaceInDiffView:
increaseContextInDiffView: '}'
decreaseContextInDiffView: '{'
increaseRenameSimilarityThreshold: )
decreaseRenameSimilarityThreshold: (
- openDiffTool:
+ openDiffTool:
+ editConfig:
status:
checkForUpdate: u
recentRepos:
@@ -672,7 +742,7 @@ keybinding:
commitChangesWithoutHook: w
amendLastCommit: A
commitChangesWithEditor: C
- findBaseCommitForFixup:
+ findBaseCommitForFixup:
confirmDiscard: x
ignoreFile: i
refreshFiles: r
@@ -683,14 +753,15 @@ keybinding:
fetch: f
toggleTreeView: '`'
openMergeOptions: M
- openStatusFilter:
+ openStatusFilter:
copyFileInfoToClipboard: "y"
collapseAll: '-'
expandAll: =
branches:
createPullRequest: o
viewPullRequestOptions: O
- copyPullRequestURL:
+ openPullRequestInBrowser: G
+ copyPullRequestURL:
checkoutBranchByName: c
forceCheckoutBranch: F
checkoutPreviousBranch: '-'
@@ -706,8 +777,6 @@ keybinding:
fetchRemote: f
addForkRemote: F
sortOrder: s
- worktrees:
- viewWorktreeOptions: w
commits:
squashDown: s
renameCommit: r
@@ -717,8 +786,8 @@ keybinding:
setFixupMessage: c
createFixupCommit: F
squashAboveCommits: S
- moveDownCommit:
- moveUpCommit:
+ moveDownCommit: [, ]
+ moveUpCommit: [, ]
amendToCommit: A
resetCommitAuthor: a
pickCommit: p
@@ -728,10 +797,11 @@ keybinding:
markCommitAsBaseForRebase: B
tagCommit: T
checkoutCommit:
- resetCherryPick:
+ resetCherryPick:
copyCommitAttributeToClipboard: "y"
- openLogMenu:
+ openLogMenu:
openInBrowser: o
+ openPullRequestInBrowser: G
viewBisectOptions: b
startInteractiveRebase: i
selectCommitsOfCurrentBranch: '*'
@@ -745,6 +815,8 @@ keybinding:
commitFiles:
checkoutCommitFile: c
main:
+ prevHunk: [, h]
+ nextHunk: [, l]
toggleSelectHunk: a
pickBothHunks: b
editSelectHunk: E
@@ -753,7 +825,7 @@ keybinding:
update: u
bulkMenu: b
commitMessage:
- commitMenu:
+ commitMenu:
```
@@ -1036,6 +1108,12 @@ keybinding:
edit: # 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
```yaml
@@ -1083,6 +1161,8 @@ Where:
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab`, `gitea` or `codeberg`
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
+For the `github` provider, configuring an entry here also enables the pull-request icons in the branches panel for that host (e.g. a GitHub Enterprise Server instance). Lazygit picks up the auth token via the same mechanisms as the `gh` CLI: the `GH_ENTERPRISE_TOKEN` / `GITHUB_ENTERPRISE_TOKEN` environment variables, or `gh auth login --hostname `.
+
## Predefined commit message prefix
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name.
diff --git a/docs-master/Custom_Command_Keybindings.md b/docs-master/Custom_Command_Keybindings.md
index 18e37463e..55e14d5f1 100644
--- a/docs-master/Custom_Command_Keybindings.md
+++ b/docs-master/Custom_Command_Keybindings.md
@@ -50,7 +50,7 @@ Custom command keybindings will appear alongside inbuilt keybindings when you vi
For a given custom command, here are the allowed fields:
| _field_ | _description_ | required |
|-----------------|----------------------|-|
-| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
+| key | The key to trigger the command. Use a single key or list of keys, as described in [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
| command | The command to run (using Go template syntax for placeholder values) | yes |
| context | The context in which to listen for the key (see [below](#contexts)) | yes |
| prompts | A list of prompts that will request user input before running the final command | no |
@@ -102,6 +102,7 @@ These fields are applicable to all prompts.
| type | One of 'input', 'confirm', 'menu', 'menuFromCommand' | yes |
| title | The title to display in the popup panel | no |
| key | Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command | yes |
+| condition | A Go template expression; if it resolves to empty string or `false`, the prompt is skipped. See [Conditional prompts](#conditional-prompts) | no |
### Input
@@ -192,7 +193,7 @@ The permitted option fields are:
| name | The first part of the label | no |
| description | The second part of the label | no |
| value | the value that will be used in the command | yes |
-| key | Keybinding to invoke this menu option without needing to navigate to it. Can be a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | no |
+| key | Keybinding to invoke this menu option without needing to navigate to it. Use a single key or list of keys, as described in [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | no |
If an option has no name the value will be displayed to the user in place of the name, so you're allowed to only include the value like so:
@@ -319,6 +320,41 @@ Here's an example using a command but not specifying anything else: so each line
command: 'ls'
```
+### Conditional prompts
+
+Here's an example of a conditional prompt:
+
+```yml
+customCommands:
+ - key: 'a'
+ context: 'localBranches'
+ prompts:
+ - type: 'menu'
+ title: 'How do you want to create the branch?'
+ key: 'Method'
+ options:
+ - value: 'simple'
+ name: 'Simple'
+ description: 'just a branch name'
+ - value: 'prefix'
+ name: 'With prefix'
+ description: 'with a category prefix'
+ - type: 'menu'
+ title: 'Branch prefix'
+ key: 'Prefix'
+ condition: '{{ eq .Form.Method "prefix" }}'
+ options:
+ - value: 'feature/'
+ - value: 'hotfix/'
+ - value: 'release/'
+ - type: 'input'
+ title: 'Branch name'
+ key: 'Name'
+ command: "git checkout -b '{{.Form.Prefix}}{{.Form.Name}}'"
+```
+
+In this example the 'Branch prefix' menu only appears if the user chose 'With prefix'. Otherwise it is skipped and `.Form.Prefix` defaults to empty string.
+
## Placeholder values
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
diff --git a/docs-master/Custom_DiffRenderers.md b/docs-master/Custom_DiffRenderers.md
new file mode 100644
index 000000000..509f42ebf
--- /dev/null
+++ b/docs-master/Custom_DiffRenderers.md
@@ -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
+```
+
+
+
+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
+```
+
+
+
+## ydiff
+
+```yaml
+gui:
+ sidePanelWidth: 0.2 # gives you more space to show things side-by-side
+git:
+ diffRenderers:
+ - colorArg: never
+ command: ydiff -p cat
+```
+
+
diff --git a/docs-master/Custom_Pagers.md b/docs-master/Custom_Pagers.md
deleted file mode 100644
index 83f4e4e62..000000000
--- a/docs-master/Custom_Pagers.md
+++ /dev/null
@@ -1,118 +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:
-
-```yaml
-git:
- pagers:
- - pager: delta --dark --paging=never
- - pager: ydiff -p cat -s --wrap --width={{columnWidth}}
- colorArg: never
- - externalDiffCommand: difft --color=always
-```
-
-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
-```
-
-
-
-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
-```
-
-
-
-## 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}}
-```
-
-
-
-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.)
diff --git a/docs-master/README.md b/docs-master/README.md
index 1bc0bb6be..c586d9699 100644
--- a/docs-master/README.md
+++ b/docs-master/README.md
@@ -2,7 +2,7 @@
* [Configuration](./Config.md).
* [Custom Commands](./Custom_Command_Keybindings.md)
-* [Custom Pagers](./Custom_Pagers.md)
+* [Custom Diff Renderers](./Custom_DiffRenderers.md)
* [Dev docs](./dev)
* [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md)
diff --git a/docs-master/Searching.md b/docs-master/Searching.md
index 589831c55..4cba775df 100644
--- a/docs-master/Searching.md
+++ b/docs-master/Searching.md
@@ -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`.
-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.
+## 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
You can filter the files view to only show staged/unstaged files by pressing `` in the files view.
diff --git a/docs-master/Undoing.md b/docs-master/Undoing.md
index 0a4c2f381..032573258 100644
--- a/docs-master/Undoing.md
+++ b/docs-master/Undoing.md
@@ -1,6 +1,6 @@
# Undo/Redo in lazygit
-You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
+You can undo the last action by pressing 'z' and redo with 'Z' (shift+z). Here we drop a couple of commits and then undo the actions.
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.

diff --git a/docs-master/keybindings/Custom_Keybindings.md b/docs-master/keybindings/Custom_Keybindings.md
index a2537f069..998aae14c 100644
--- a/docs-master/keybindings/Custom_Keybindings.md
+++ b/docs-master/keybindings/Custom_Keybindings.md
@@ -1,63 +1,97 @@
-## Possible keybindings
-| Put in | You will get |
-|---------------|----------------|
-| `` | F1 |
-| `` | F2 |
-| `` | F3 |
-| `` | F4 |
-| `` | F5 |
-| `` | F6 |
-| `` | F7 |
-| `` | F8 |
-| `` | F9 |
-| `` | F10 |
-| `` | F11 |
-| `` | F12 |
-| `` | Insert |
-| `` | Delete |
-| `` | Home |
-| `` | End |
-| `` | Pgup |
-| `` | Pgdn |
-| `` | ArrowUp |
-| `` | ShiftArrowUp |
-| `` | ArrowDown |
-| `` | ShiftArrowDown |
-| `` | ArrowLeft |
-| `` | ArrowRight |
-| `` | Tab |
-| `` | Backtab |
-| `` | Enter |
-| `` | AltEnter |
-| `` | Esc |
-| `` | Backspace |
-| `` | CtrlSpace |
-| `` | CtrlSlash |
-| `` | Space |
-| `` | CtrlA |
-| `` | CtrlB |
-| `` | CtrlC |
-| `` | CtrlD |
-| `` | CtrlE |
-| `` | CtrlF |
-| `` | CtrlG |
-| `` | CtrlJ |
-| `` | CtrlK |
-| `` | CtrlL |
-| `` | CtrlN |
-| `` | CtrlO |
-| `` | CtrlP |
-| `` | CtrlQ |
-| `` | CtrlR |
-| `` | CtrlS |
-| `` | CtrlT |
-| `` | CtrlU |
-| `` | CtrlV |
-| `` | CtrlW |
-| `` | CtrlX |
-| `` | CtrlY |
-| `` | CtrlZ |
-| `` | Ctrl4 |
-| `` | Ctrl5 |
-| `` | Ctrl6 |
-| `` | Ctrl8 |
+## Custom Keybindings
+
+A keybinding is one of:
+
+- A single printable character, e.g. `q`, `?`, `5`. Uppercase letters mean
+ shift+letter — write `A`, not ``.
+- A special key name in angle brackets, e.g. ``, ``, ``.
+- A key with modifiers in angle brackets, e.g. ``, ``.
+- The literal string `` to disable a binding.
+- A list of any of the above, to bind multiple keys to the same action:
+ `quit: [q, ]`.
+
+### Modifiers
+
+Prefix a key with one or more modifiers, joined by `+`:
+
+| Prefix | Short form | Modifier |
+| -------- | ---------- | ----------------------------------------------------------------------------------------- |
+| `ctrl+` | `c+` | Ctrl |
+| `alt+` | `a+` | Alt |
+| `shift+` | `s+` | Shift |
+| `meta+` | `m+` | Depends on terminal; typically ⌘ on macOS or Super/Win key, when the terminal forwards it |
+
+You can also use `-` instead of `+` as the separator. Modifiers may appear in
+any order, and short and long forms can be mixed. The whole binding should be
+wrapped in angle brackets when it has any modifiers. The following all express
+the same binding:
+
+- ``
+- ``
+- ``
+- ``
+
+### Special key names
+
+| Put in | You will get |
+| --------------------------------------- | ------------------- |
+| `` – `` | F1 – F12 |
+| `` | Insert |
+| `` | Delete |
+| `` | Home |
+| `` | End |
+| `` | PageUp |
+| `` | PageDown |
+| `` | ArrowUp |
+| `` | ArrowDown |
+| `` | ArrowLeft |
+| `` | ArrowRight |
+| `` | Tab |
+| `` | Shift+Tab |
+| `` | Enter |
+| `` | Escape |
+| `` | Backspace |
+| `` | Space |
+| ``/`` | Mouse wheel up/down |
+
+These can be combined with modifiers, e.g. ``, ``, ``.
+
+### Special characters with modifiers
+
+`` and `` are keyword forms for `-` and `+` when combined with a
+modifier (e.g. `` for Ctrl+`-`). Without modifiers, write `-` and
+`+` directly. `` is the keyword for the space character.
+
+### Combinations that are rejected
+
+These look reasonable but can't actually be delivered by a terminal:
+
+- `` (shift alone on a rune) — terminals fold shift into the rune
+ itself, so shift+a arrives as `A`. Write `A` instead.
+- ``, ``, etc. (modifier on an uppercase ASCII letter) — write
+ `` instead.
+
+### Terminal compatibility
+
+Support for combinations of modifiers, and in general keybindings beyond plain
+letters and ctrl+letter, require a newer terminal protocol that not all
+terminals support.
+
+Terminals that are known to have good support include: Ghostty, kitty,
+WezTerm, foot, Konsole, Alacritty, iTerm2, Windows Terminal.
+
+The default terminal on macOS (Terminal.app) does not; I recommend to switch to
+either Ghostty or iTerm2 as a replacement (or one of the others above).
+
+On Windows, a popular terminal is the MinTTY console that comes with Git for
+Windows; this also doesn't support the newer protocol. The recommended
+replacement is Windows Terminal, which is very good these days, and Git Bash
+runs just fine in it.
+
+Inside **tmux** or **screen**, extended keys are stripped unless the multiplexer
+is configured to forward them. For tmux 3.2+:
+
+`
+set -g extended-keys on
+set -as terminal-features 'xterm*:extkeys'
+`
diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md
index 827f6eb34..3ec731bf2 100644
--- a/docs-master/keybindings/Keybindings_en.md
+++ b/docs-master/keybindings/Keybindings_en.md
@@ -2,15 +2,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit Keybindings
-_Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
-
## Global keybindings
| Key | Action | Info |
|-----|--------|-------------|
-| `` `` | Switch to a recent repo | |
-| `` (fn+up/shift+k) `` | Scroll up main window | |
-| `` (fn+down/shift+j) `` | Scroll down main window | |
+| `` `` | Switch to a recent repo | |
+| `` , K, (fn+up/shift+k) `` | Scroll up main window | |
+| `` , J, (fn+down/shift+j) `` | Scroll down main window | |
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` 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 `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
@@ -19,20 +17,21 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.
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.
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. |
-| `` `` | View custom patch options | |
+| `` `` | View custom patch options | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` 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) | |
| `` _ `` | 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. |
| `` `` | Cancel | |
| `` ? `` | Open keybindings menu | |
-| `` `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
-| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
-| `` `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
-| `` q `` | Quit | |
-| `` `` | Suspend the application | |
-| `` `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.
The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
+| `` `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
+| `` W, `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
+| `` q, `` | Quit | |
+| `` `` | Suspend the application | |
+| `` `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.
The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
+| `` `` | 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 `` | 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. |
@@ -42,11 +41,11 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` , `` | Previous page | |
| `` . `` | Next page | |
-| `` < () `` | Scroll to top | |
-| `` > () `` | Scroll to bottom | |
+| `` <, `` | Scroll to top | |
+| `` >, `` | Scroll to bottom | |
| `` v `` | Toggle range select | |
-| `` `` | Range select down | |
-| `` `` | Range select up | |
+| `` `` | Range select down | |
+| `` `` | Range select up | |
| `` / `` | Search the current view by text | |
| `` H `` | Scroll left | |
| `` L `` | Scroll right | |
@@ -57,13 +56,13 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
-| `` `` | Copy path to clipboard | |
+| `` `` | Copy path to clipboard | |
| `` y `` | Copy to clipboard | |
| `` c `` | Checkout | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
| `` d `` | Discard | 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. |
| `` o `` | Open file | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
-| `` `` | Open external diff tool (git difftool) | |
+| `` `` | Open external diff tool (git difftool) | |
| `` `` | Toggle file included 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. |
| `` `` | Enter file / Toggle directory collapsed | 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. |
@@ -84,8 +83,8 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
-| `` `` | Copy abbreviated commit hash to clipboard | |
-| `` `` | Reset copied (cherry-picked) commits selection | |
+| `` `` | Copy abbreviated commit hash to clipboard | |
+| `` `` | Reset copied (cherry-picked) commits selection | |
| `` b `` | View bisect options | |
| `` 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. |
@@ -98,27 +97,28 @@ _Legend: `` means ctrl+b, `` means alt+b, `B` means shift+b_
| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. |
| `` F `` | Create fixup commit | Create 'fixup!' commit for the selected commit. Later on, you can press `S` on this same commit to apply all above fixup commits. |
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
-| ``