Compare commits

..
Author SHA1 Message Date
Jesse Duffield a4465fec48 Update release notes config and add CI check 2023-07-20 17:10:47 +10:00
1252 changed files with 25542 additions and 109184 deletions
-4
View File
@@ -1,4 +0,0 @@
root = true
[*.go]
indent_style = tab
+2 -7
View File
@@ -2,14 +2,9 @@
- **Please check if the PR fulfills these requirements** - **Please check if the PR fulfills these requirements**
* [ ] Cheatsheets are up-to-date (run `go generate ./...`) * [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting)) * [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide) * [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation)) * [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] Docs have been updated if necessary * [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc * [ ] You've read through your own file changes for silly mistakes etc
<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for examples
-->
+4 -8
View File
@@ -3,7 +3,7 @@ name: Continuous Delivery
on: on:
push: push:
tags: tags:
- "v*" - 'v*'
jobs: jobs:
goreleaser: goreleaser:
@@ -14,15 +14,11 @@ jobs:
- name: Unshallow repo - name: Unshallow repo
run: git fetch --prune --unshallow run: git fetch --prune --unshallow
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Run goreleaser - name: Run goreleaser
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v1
with:
distribution: goreleaser
version: v1.17.2
args: release --clean
env: env:
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}} GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
homebrew: homebrew:
+70 -74
View File
@@ -1,7 +1,7 @@
name: Continuous Integration name: Continuous Integration
env: env:
GO_VERSION: 1.22 GO_VERSION: 1.18
on: on:
push: push:
@@ -30,20 +30,22 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Cache build
uses: actions/cache@v3
with:
path: |
${{matrix.cache_path}}
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
- name: Test code - name: Test code
# we're passing -short so that we skip the integration tests, which will be run in parallel below # we're passing -short so that we skip the integration tests, which will be run in parallel below
run: | run: |
mkdir -p /tmp/code_coverage go test ./... -short
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v3
with:
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
path: /tmp/code_coverage
integration-tests: integration-tests:
strategy: strategy:
fail-fast: false fail-fast: false
@@ -87,23 +89,23 @@ jobs:
path: ~/git-${{matrix.git-version}} path: ~/git-${{matrix.git-version}}
key: ${{runner.os}}-git-${{matrix.git-version}} key: ${{runner.os}}-git-${{matrix.git-version}}
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
- name: Print git version - name: Print git version
run: git --version run: git --version
- name: Test code - name: Test code
env:
# See https://go.dev/blog/integration-test-coverage
LAZYGIT_GOCOVERDIR: /tmp/code_coverage
run: | run: |
mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh ./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v3
with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
path: /tmp/code_coverage
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -113,9 +115,18 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
restore-keys: |
${{runner.os}}-go-
- name: Build linux binary - name: Build linux binary
run: | run: |
GOOS=linux go build GOOS=linux go build
@@ -140,21 +151,29 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
restore-keys: |
${{runner.os}}-go-
- name: Check Cheatsheet
run: |
go run scripts/cheatsheet/main.go check
- name: Check Vendor Directory - name: Check Vendor Directory
# ensure our vendor directory matches up with our go modules # ensure our vendor directory matches up with our go modules
run: | run: |
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1) go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
- name: Check go.mod file - name: Check Integration Test List
# ensure our go.mod file is clean # ensure our integration test list is up to date
run: | run: |
go mod tidy && git diff --exit-code || (echo "go.mod file is not clean. Run 'go mod tidy' locally and commit the changes" && exit 1) go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
- name: Check All Auto-Generated Files
# ensure all our auto-generated files are up to date
run: |
go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
shell: bash # needed so that we get "-o pipefail" shell: bash # needed so that we get "-o pipefail"
- name: Check Filenames - name: Check Filenames
run: scripts/check_filenames.sh run: scripts/check_filenames.sh
@@ -166,56 +185,33 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v4 uses: actions/setup-go@v1
with: with:
go-version: 1.22.x go-version: 1.18.x
- name: Cache build
uses: actions/cache@v1
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
restore-keys: |
${{runner.os}}-go-
- name: Lint - name: Lint
uses: golangci/golangci-lint-action@v3.7.0 uses: golangci/golangci-lint-action@v3.1.0
with: with:
version: v1.58 version: latest
- name: errors - name: errors
run: golangci-lint run run: golangci-lint run
if: ${{ failure() }} if: ${{ failure() }}
check-required-label: label:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master' # permissions:
# issues: write
# pull-requests: write
steps: steps:
- uses: mheap/github-action-required-labels@v5 - uses: mheap/github-action-required-labels@v5
with: with:
mode: exactly mode: exactly
count: 1 count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n" labels: "ignore-for-release, feature, enhancement, maintenance, docs, i18n"
upload-coverage:
# List all jobs that produce coverage files
needs: [unit-tests, integration-tests]
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.22.x
- name: Download all coverage artifacts
uses: actions/download-artifact@v3
with:
path: /tmp/code_coverage
- name: Combine coverage files
run: |
# Find all directories in /tmp/code_coverage and create a comma-separated list
COVERAGE_DIRS=$(find /tmp/code_coverage -mindepth 1 -maxdepth 1 -type d -printf '/tmp/code_coverage/%f,' | sed 's/,$//')
echo "Coverage directories: $COVERAGE_DIRS"
# Run the combine command with the generated list
go tool covdata textfmt -i=$COVERAGE_DIRS -o coverage.out
echo "Combined coverage:"
go tool cover -func coverage.out | tail -1 | awk '{print $3}'
- name: Upload to Codacy
run: |
CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \
bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
--force-coverage-parser go -r coverage.out
+10 -10
View File
@@ -12,17 +12,17 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Generate Sponsors 💖 - name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1.2.2 uses: JamesIves/github-sponsors-readme-action@v1.0.8
with: with:
token: ${{ secrets.SPONSORS_TOKEN }} token: ${{ secrets.SPONSORS_TOKEN }}
file: "README.md" file: 'README.md'
if: ${{ github.repository == 'jesseduffield/lazygit' }} if: ${{ github.repository == 'jesseduffield/lazygit' }}
- name: Create Pull Request 🚀 - name: Commit and push if changed
uses: peter-evans/create-pull-request@v6 run: |-
with: git diff
commit-message: "README.md: Update Sponsors" git config --global user.email "actions@users.noreply.github.com"
title: "README.md: Update Sponsors" git config --global user.name "README-bot"
author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" git add README.md
labels: "ignore-for-release" git commit -m "Updated README.md" || exit 0
delete-branch: true git push
+4 -6
View File
@@ -6,6 +6,9 @@
# Hidden # Hidden
.* .*
# TODO
TODO.*
# Notes # Notes
*.notes *.notes
@@ -32,12 +35,7 @@ lazygit.exe
test/git_server/data test/git_server/data
test/_results/** test/results/**
oryxBuildBinary oryxBuildBinary
__debug_bin __debug_bin
.worktrees
demo/output/*
coverage.out
+3 -13
View File
@@ -14,26 +14,16 @@ linters:
- exhaustive - exhaustive
- makezero - makezero
- nakedret - nakedret
- copyloopvar # - goconst # TODO: enable and fix issues
fast: false fast: false
linters-settings: linters-settings:
copyloopvar:
# Check all assigning the loop variable to another variable.
# Default: false
# If true, an assignment like `a := x` will be detected as an error.
check-alias: true
exhaustive: exhaustive:
default-signifies-exhaustive: true default-signifies-exhaustive: true
staticcheck:
# SA1019 is for checking that we're not using fields marked as deprecated
# in a comment. It decides this in a loose way so I'm silencing it. Also because
# it's tripping on our own structs.
checks: ["all", "-SA1019"]
nakedret: nakedret:
# the gods will judge me but I just don't like naked returns at all # the gods will judge me but I just don't like naked returns at all
max-func-lines: 0 max-func-lines: 0
run: run:
go: '1.22' go: 1.18
timeout: 10m
+1 -1
View File
@@ -12,7 +12,7 @@ builds:
- amd64 - amd64
- arm - arm
- arm64 - arm64
- '386' - 386
# Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`. # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`.
ldflags: ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
+10 -35
View File
@@ -7,12 +7,11 @@
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "main.go", "program": "main.go",
"args": [ "args": ["--debug", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
"--debug",
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
],
"hideSystemGoroutines": true,
"console": "integratedTerminal", "console": "integratedTerminal",
"presentation": {
"hidden": true
}
}, },
{ {
"name": "Tail Lazygit logs", "name": "Tail Lazygit logs",
@@ -20,41 +19,17 @@
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "main.go", "program": "main.go",
"args": [ "args": ["--logs", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
"--logs",
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
],
"console": "integratedTerminal", "console": "integratedTerminal",
}, "presentation": {
{ "hidden": true
"name": "Attach to a running Lazygit", }
"type": "go", }
"request": "attach",
"mode": "local",
"processId": "lazygit",
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
{
// To use this, first start an integration test with the "cli" runner and
// use the -debug option; e.g.
// $ make integration-test-cli -- -debug tag/reset.go
"name": "Attach to integration test runner",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "test_lazygit",
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
], ],
"compounds": [ "compounds": [
{ {
"name": "Run with logs", "name": "Run with logs",
"configurations": [ "configurations": ["Tail Lazygit logs", "Debug Lazygit"],
"Tail Lazygit logs",
"Debug Lazygit"
],
"stopAll": true "stopAll": true
} }
] ]
-5
View File
@@ -1,5 +0,0 @@
{
"gopls": {
"formatting.gofumpt": true,
},
}
+25 -16
View File
@@ -10,15 +10,6 @@ before making a change.
[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. [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.
## Codebase guide
[This doc](./docs/dev/Codebase_Guide.md) explains:
* what the different packages in the codebase are for
* where important files live
* important concepts in the code
* how the event loop works
* other useful information
## All code changes happen through Pull Requests ## All code changes happen through Pull Requests
Pull requests are the best way to propose changes to the codebase. We actively Pull requests are the best way to propose changes to the codebase. We actively
@@ -30,8 +21,6 @@ welcome your pull requests:
4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
5. Issue that pull request! 5. Issue that pull request!
Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch.
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place. 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.
## Running in a VSCode dev container ## Running in a VSCode dev container
@@ -108,10 +97,6 @@ To run gofumpt from your terminal go:
go install mvdan.cc/gofumpt@latest && gofumpt -l -w . 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 ## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. 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). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!). Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. 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). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
@@ -154,7 +139,31 @@ If you want to trigger a debug session from VSCode, you can use the following sn
## Profiling ## Profiling
If you want to investigate what's contributing to CPU or memory usage, see [this separate document](docs/dev/Profiling.md). If you want to investigate what's contributing to CPU usage you can add the following to the top of the `main()` function in `main.go`
```go
import "runtime/pprof"
func main() {
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
...
```
Then run lazygit, and afterwards, from your terminal, run:
```sh
go tool pprof --web cpu.prof
```
That should open an application which allows you to view the breakdown of CPU usage.
## Testing ## Testing
+1 -1
View File
@@ -2,7 +2,7 @@
# docker build -t lazygit . # docker build -t lazygit .
# docker run -it lazygit:latest /bin/sh # docker run -it lazygit:latest /bin/sh
FROM golang:1.22 as build FROM golang:1.18 as build
WORKDIR /go/src/github.com/jesseduffield/lazygit/ WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
-72
View File
@@ -1,72 +0,0 @@
.PHONY: all
all: build
.PHONY: build
build:
go build -gcflags='all=-N -l'
.PHONY: install
install:
go install
.PHONY: run
run: build
./lazygit
# Run `make run-debug` in one terminal tab and `make print-log` in another to view the program and its log output side by side
.PHONY: run-debug
run-debug:
go run main.go -debug
.PHONY: print-log
print-log:
go run main.go --logs
.PHONY: unit-test
unit-test:
go test ./... -short
.PHONY: test
test: unit-test integration-test-all
# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future)
.PHONY: generate
generate:
go generate ./...
.PHONY: format
format:
gofumpt -l -w .
.PHONY: lint
lint:
golangci-lint run
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
.PHONY: integration-test-tui
integration-test-tui:
go run cmd/integration_test/main.go tui $(filter-out $@,$(MAKECMDGOALS))
.PHONY: integration-test-cli
integration-test-cli:
go run cmd/integration_test/main.go cli $(filter-out $@,$(MAKECMDGOALS))
.PHONY: integration-test-all
integration-test-all:
go test pkg/integration/clients/*.go
.PHONY: bump-gocui
bump-gocui:
scripts/bump_gocui.sh
.PHONY: bump-lazycore
bump-lazycore:
scripts/bump_lazycore.sh
.PHONY: record-demo
record-demo:
demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS))
.PHONY: vendor
vendor:
go mod vendor && go mod tidy
+38 -230
View File
File diff suppressed because one or more lines are too long
+15 -41
View File
@@ -16,7 +16,7 @@ Usage:
> go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ... > go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ...
If you pass no test names, it runs all tests If you pass no test names, it runs all tests
Accepted environment variables: Accepted environment variables:
INPUT_DELAY (e.g. 200): the number of milliseconds to wait between keypresses or mouse clicks KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
TUI mode: TUI mode:
> go run cmd/integration_test/main.go tui > go run cmd/integration_test/main.go tui
@@ -26,29 +26,6 @@ Usage:
> go run cmd/integration_test/main.go help > go run cmd/integration_test/main.go help
` `
type flagInfo struct {
name string // name of the flag; can be used with "-" or "--"
flag *bool // a pointer to the variable that should be set to true when this flag is passed
}
// Takes the args that you want to parse (excluding the program name and any
// subcommands), and returns the remaining args with the flags removed
func parseFlags(args []string, flags []flagInfo) []string {
outer:
for len(args) > 0 {
for _, f := range flags {
if args[0] == "-"+f.name || args[0] == "--"+f.name {
*f.flag = true
args = args[1:]
continue outer
}
}
break
}
return args
}
func main() { func main() {
if len(os.Args) < 2 { if len(os.Args) < 2 {
log.Fatal(usage) log.Fatal(usage)
@@ -58,26 +35,23 @@ func main() {
case "help": case "help":
fmt.Println(usage) fmt.Println(usage)
case "cli": case "cli":
testNames := os.Args[2:]
slow := false slow := false
sandbox := false sandbox := false
waitForDebugger := false // get the next arg if it's --slow
raceDetector := false if len(os.Args) > 2 {
testNames := parseFlags(os.Args[2:], []flagInfo{ if os.Args[2] == "--slow" || os.Args[2] == "-slow" {
{"slow", &slow}, testNames = os.Args[3:]
{"sandbox", &sandbox}, slow = true
{"debug", &waitForDebugger}, } else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" {
{"race", &raceDetector}, testNames = os.Args[3:]
}) sandbox = true
clients.RunCLI(testNames, slow, sandbox, waitForDebugger, raceDetector) }
case "tui":
raceDetector := false
remainingArgs := parseFlags(os.Args[2:], []flagInfo{
{"race", &raceDetector},
})
if len(remainingArgs) > 0 {
log.Fatal("tui only supports the -race argument.")
} }
clients.RunTUI(raceDetector)
clients.RunCLI(testNames, slow, sandbox)
case "tui":
clients.RunTUI()
default: default:
log.Fatal(usage) log.Fatal(usage)
} }
-2
View File
@@ -1,2 +0,0 @@
This directory contains stuff for recording lazygit demos.
-112
View File
@@ -1,112 +0,0 @@
# Specify a command to be executed
# like `/bin/bash -l`, `ls`, or any other commands
# the default is bash for Linux
# or powershell.exe for Windows
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
# Specify the current working directory path
# the default is the current working directory path
cwd: null
# Export additional ENV variables
env:
recording: true
# Explicitly set the number of columns
# or use `auto` to take the current
# number of columns of your shell
cols: 120 # 100
# Explicitly set the number of rows
# or use `auto` to take the current
# number of rows of your shell
rows: 35 # 30
# Amount of times to repeat GIF
# If value is -1, play once
# If value is 0, loop indefinitely
# If value is a positive number, loop n times
repeat: 0
# Quality
# 1 - 100
# Higher quality seems to make no difference, but running it through
# gifsicle ends up with a much better compressed version.
quality: 100
# Delay between frames in ms
# If the value is `auto` use the actual recording delays
frameDelay: auto
# Maximum delay between frames in ms
# Ignored if the `frameDelay` isn't set to `auto`
# Set to `auto` to prevent limiting the max idle time
maxIdleTime: 2000
# The surrounding frame box
# The `type` can be null, window, floating, or solid`
# To hide the title use the value null
# Don't forget to add a backgroundColor style with a null as type
frameBox:
type: floating
title: Lazygit
style:
border: 0px black solid
backgroundColor: "#1d1d1d"
margin: -5px
# Add a watermark image to the rendered gif
# You need to specify an absolute path for
# the image on your machine or a URL, and you can also
# add your own CSS styles
watermark:
imagePath: null
style:
position: absolute
right: 15px
bottom: 15px
width: 100px
opacity: 0.9
# Cursor style can be one of
# `block`, `underline`, or `bar`
cursorStyle: block
# Font family
# You can use any font that is installed on your machine
# in CSS-like syntax
# Download from:
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip
# Not using the mono font because it makes icons too small.
fontFamily: "DejaVuSansM Nerd Font"
# The size of the font
fontSize: 8
# The height of lines
lineHeight: 1
# The spacing between letters
letterSpacing: 0
# Theme
theme:
background: "transparent"
foreground: "#dddad6"
cursor: "#c7c7c7"
black: "#7a7a7a"
red: "#fc4384"
green: "#b3e33b"
yellow: "#ffa727"
blue: "#102895"
magenta: "#c930c7"
cyan: "#00c5c7"
white: "#c7c7c7"
brightBlack: "#676767"
brightRed: "#ff7fac"
brightGreen: "#c8ed71"
brightYellow: "#ebdf86"
brightBlue: "#6871ff"
brightMagenta: "#ff76ff"
brightCyan: "#5ffdff"
brightWhite: "#fffefe"
-81
View File
@@ -1,81 +0,0 @@
#!/bin/sh
set -e
TYPE=$1
TEST=$2
usage() {
echo "Usage: $0 [gif|mp4] <test path>"
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
exit 1
}
if [ "$#" -ne 2 ]
then
usage
fi
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
then
usage
exit 1
fi
if [ -z "$TEST" ]
then
usage
fi
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
if [ -z "$WORKTREE_PATH" ]
then
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
echo "git worktree add .worktrees/assets assets"
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
exit 1
fi
OUTPUT_DIR="$WORKTREE_PATH/demo"
if ! command -v terminalizer &> /dev/null
then
echo "terminalizer could not be found"
echo "Install it with: npm install -g terminalizer"
exit 1
fi
if ! command -v "gifsicle" &> /dev/null
then
echo "gifsicle could not be found"
echo "Install it with: npm install -g gifsicle"
exit 1
fi
# Get last part of the test path and set that as the output name
# example test path: pkg/integration/tests/01_basic_test.go
# For that we want: NAME=01_basic_test
NAME=$(echo "$TEST" | sed -e 's/.*\///' | sed -e 's/\..*//')
# Add the demo to the tests list (if missing) so that it can be run
go generate pkg/integration/tests/tests.go
mkdir -p "$OUTPUT_DIR"
# First we record the demo into a yaml representation
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
# Then we render it into a gif
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
# Then we convert it to either an mp4 or gif based on the command line argument
if [ "$TYPE" = "mp4" ]
then
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
else
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
fi
echo "Demo recorded to $COMPRESSED_PATH"
+217 -569
View File
@@ -3,8 +3,8 @@
Default path for the config file: Default path for the config file:
- Linux: `~/.config/lazygit/config.yml` - Linux: `~/.config/lazygit/config.yml`
- MacOS: `~/Library/Application\ Support/lazygit/config.yml` - MacOS: `~/Library/Application Support/lazygit/config.yml`
- Windows: `%LOCALAPPDATA%\lazygit\config.yml` (default location, but it will also be found in `%APPDATA%\lazygit\config.yml` - Windows: `%APPDATA%\lazygit\config.yml`
For old installations (slightly embarrassing: I didn't realise at the time that you didn't need to supply a vendor name to the path so I just used my name): For old installations (slightly embarrassing: I didn't realise at the time that you didn't need to supply a vendor name to the path so I just used my name):
@@ -16,577 +16,244 @@ If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"` - MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/jesseduffield/lazygit/master/schema/config.json
```
to the top of your config file or via [Visual Studio Code settings.json config][settings].
[yaml]: https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
[settings]: https://github.com/redhat-developer/vscode-yaml#associating-a-schema-to-a-glob-pattern-via-yamlschemas
## Default ## Default
<!-- START CONFIG YAML: AUTOMATICALLY GENERATED with `go generate ./..., DO NOT UPDATE MANUALLY -->
```yaml ```yaml
# Config relating to the Lazygit UI
gui: gui:
# The number of lines you scroll by when scrolling the main window # stuff relating to the UI
scrollHeight: 2 windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
scrollHeight: 2 # how many lines you scroll by
# If true, allow scrolling past the bottom of the content in the main window scrollPastBottom: true # enable scrolling past the bottom
scrollPastBottom: true sidePanelWidth: 0.3333 # number from 0 to 1
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
scrollOffMargin: 2
# One of: 'margin' (default) | 'jump'
scrollOffBehavior: margin
# If true, capture mouse events.
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
mouseEvents: true
# If true, do not show a warning when discarding changes in the staging view.
skipDiscardChangeWarning: false
# If true, do not show warning when applying/popping the stash
skipStashWarning: false
# If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
skipNoStagedFilesWarning: false
# If true, do not show a warning when rewording a commit via an external editor
skipRewordInEditorWarning: false
# Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
# Number from 0 to 1.0.
sidePanelWidth: 0.3333
# If true, increase the height of the focused side window; creating an accordion effect.
expandFocusedSidePanel: false expandFocusedSidePanel: false
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
# The weight of the expanded side panel, relative to the other panels. 2 means language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
# twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true. timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
expandedSidePanelWeight: 2 shortTimeFormat: '3:04PM'
# 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.
# Options are:
# - 'horizontal': split the window horizontally
# - 'vertical': split the window vertically
# - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
mainPanelSplitMode: flexible
# How the window is split when in half screen mode (i.e. after hitting '+' once).
# Possible values:
# - 'left': split the window horizontally (side panel on the left, main view on the right)
# - 'top': split the window vertically (side panel on top, main view below)
enlargedSideViewLocation: left
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
language: auto
# Format used when displaying time e.g. commit time.
# Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
timeFormat: 02 Jan 06
# Format used when displaying time if the time is less than 24 hours ago.
# Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
shortTimeFormat: 3:04PM
# Config relating to colors and styles.
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
theme: theme:
# Border color of focused window
activeBorderColor: activeBorderColor:
- green - green
- bold - bold
# Border color of non-focused windows
inactiveBorderColor: inactiveBorderColor:
- default - white
# Border color of focused window when searching in that window
searchingActiveBorderColor: searchingActiveBorderColor:
- cyan - cyan
- bold - bold
# Color of keybindings help text in the bottom line
optionsTextColor: optionsTextColor:
- blue - blue
# Background color of selected line.
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
selectedLineBgColor: selectedLineBgColor:
- blue # set to `default` to have no background colour
selectedRangeBgColor:
- blue - blue
# Foreground color of copied commit
cherryPickedCommitFgColor:
- blue
# Background color of copied commit
cherryPickedCommitBgColor: cherryPickedCommitBgColor:
- cyan - cyan
cherryPickedCommitFgColor:
# Foreground color of marked base commit (for rebase)
markedBaseCommitFgColor:
- blue - blue
# Background color of marked base commit (for rebase)
markedBaseCommitBgColor:
- yellow
# Color for file with unstaged changes
unstagedChangesColor: unstagedChangesColor:
- red - red
# Default text color
defaultFgColor: defaultFgColor:
- default - default
# Config relating to the commit length indicator
commitLength: commitLength:
# If true, show an indicator of commit message length
show: true show: true
mouseEvents: true
# If true, show the '5 of 20' footer at the bottom of list views skipDiscardChangeWarning: false
showListFooter: true skipStashWarning: false
showFileTree: true # for rendering changes files in a tree format
# If true, display the files in the file views as a tree. If false, display the files as a flat list. showListFooter: true # for seeing the '5 of 20' message in list panels
# This can be toggled from within Lazygit with the '~' key, but that will not change the default.
showFileTree: true
# If true, show a random tip in the command log when Lazygit starts
showRandomTip: true showRandomTip: true
showBranchCommitHash: false # show commit hashes alongside branch names
# If true, show the command log experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
showCommandLog: true showCommandLog: true
showIcons: false # deprecated: use nerdFontsVersion instead
# If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action. nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
showBottomLine: true
# If true, show jump-to-window keybindings in window titles.
showPanelJumps: true
# Deprecated: use nerdFontsVersion instead
showIcons: false
# Nerd fonts version to use.
# One of: '2' | '3' | empty string (default)
# If empty, do not show icons.
nerdFontsVersion: ""
# If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
showFileIcons: true
# Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.
commitHashLength: 8
# If true, show commit hashes alongside branch names in the branches view.
showBranchCommitHash: false
# Whether to show the divergence from the base branch in the branches view.
# One of: 'none' | 'onlyArrow' | 'arrowAndNumber'
showDivergenceFromBaseBranch: none
# Height of the command log view
commandLogSize: 8 commandLogSize: 8
splitDiff: 'auto' # one of 'auto' | 'always'
# Whether to split the main window when viewing file changes. skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
# One of: 'auto' | 'always' border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
# If 'auto', only split the main window when a file has both staged and unstaged changes
splitDiff: auto
# Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
# One of: 'normal' (default) | 'half' | 'full'
windowSize: normal
# Window border style.
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
border: rounded
# If true, show a seriously epic explosion animation when nuking the working tree.
animateExplosion: true
# Whether to stack UI components on top of each other.
# One of 'auto' (default) | 'always' | 'never'
portraitMode: auto
# How things are filtered when typing '/'.
# One of 'substring' (default) | 'fuzzy'
filterMode: substring
# Config relating to the spinner.
spinner:
# The frames of the spinner animation.
frames:
- '|'
- /
- '-'
- \
# The "speed" of the spinner in milliseconds.
rate: 50
# Status panel view.
# One of 'dashboard' (default) | 'allBranchesLog'
statusPanelView: dashboard
# Config relating to git
git: git:
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
paging: paging:
# 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'
colorArg: always colorArg: always
# e.g.
# diff-so-fancy
# delta --dark --paging=never
# ydiff -p cat -s --wrap --width={{columnWidth}}
pager: ""
# If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
useConfig: false useConfig: false
# e.g. 'difft --color=always'
externalDiffCommand: ""
# Config relating to committing
commit: commit:
# If true, pass '--signoff' flag when committing
signOff: false signOff: false
# Automatic WYSIWYG wrapping of the commit message as you type
autoWrapCommitMessage: true
# If autoWrapCommitMessage is true, the width to wrap to
autoWrapWidth: 72
# Config relating to merging
merging: merging:
# If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang # only applicable to unix users
# Only applicable to unix users.
manualCommit: false manualCommit: false
# extra args passed to `git merge`, e.g. --no-ff
# Extra args passed to `git merge`, e.g. --no-ff args: ''
args: ""
# list of branches that are considered 'main' branches, used when displaying commits
mainBranches:
- master
- main
# Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
skipHookPrefix: WIP
# If true, periodically fetch from remote
autoFetch: true
# If true, periodically refresh files and submodules
autoRefresh: true
# If true, pass the --all arg to git fetch
fetchAll: true
# Command used when displaying the current branch git log in the main window
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
# Command used to display git log of all branches in the main window
allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
# If true, do not spawn a separate process when using GPG
overrideGpg: false
# If true, do not allow force pushes
disableForcePushing: false
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
commitPrefix:
# pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
pattern: ""
# Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
replace: ""
# If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
# (This should really be under 'gui', not 'git')
parseEmoji: false
# Config for showing the log in the commits view
log: log:
# One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default' # one of date-order, author-date-order, topo-order or default.
# 'topo-order' makes it easier to read the git log graph, but commits may not # topo-order makes it easier to read the git log graph, but commits may not
# appear chronologically. See https://git-scm.com/docs/ # appear chronologically. See https://git-scm.com/docs/git-log#_commit_ordering
# order: 'topo-order'
# Deprecated: Configure this with `Log menu -> Commit sort order` (<c-l> in the commits window by default). # one of always, never, when-maximised
order: topo-order # this determines whether the git graph is rendered in the commits panel
showGraph: 'when-maximised'
# This determines whether the git graph is rendered in the commits panel # displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
# One of 'always' | 'never' | 'when-maximised'
#
# Deprecated: Configure this with `Log menu -> Show git graph` (<c-l> in the commits window by default).
showGraph: always
# displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false showWholeGraph: false
skipHookPrefix: WIP
# When copying commit hashes to the clipboard, truncate them to this # The main branches. We colour commits green if they belong to one of these branches,
# length. Set to 40 to disable truncation. # so that you can easily see which commits are unique to your branch (coloured in yellow)
truncateCopiedCommitHashesTo: 12 mainBranches: [master, main]
autoFetch: true
# Periodic update checks autoRefresh: true
update: fetchAll: true # Pass --all flag when running git fetch. Set to false to fetch only origin (or the current branch's upstream remote if there is one)
# One of: 'prompt' (default) | 'background' | 'never' branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
method: prompt allBranchesLogCmd: 'git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium'
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
# Period in days between update checks disableForcePushing: false
days: 14 parseEmoji: false
diffContextSize: 3 # how many lines of context are shown around a change in diffs
# Background refreshes
refresher:
# File/submodule refresh interval in seconds.
# Auto-refresh can be disabled via option 'git.autoRefresh'.
refreshInterval: 10
# Re-fetch interval in seconds.
# Auto-fetch can be disabled via option 'git.autoFetch'.
fetchInterval: 60
# If true, show a confirmation popup before quitting Lazygit
confirmOnQuit: false
# If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
quitOnTopLevelReturn: false
# Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
os: os:
# Command for editing a file. Should contain "{{filename}}". editPreset: '' # see 'Configuring File Editing' section
edit: "" edit: ''
editAtLine: ''
# Command for editing a file at a given line number. Should contain editAtLineAndWait: ''
# "{{filename}}", and may optionally contain "{{line}}". open: ''
editAtLine: "" openLink: ''
refresher:
# Same as EditAtLine, except that the command needs to wait until the refreshInterval: 10 # File/submodule refresh interval in seconds. Auto-refresh can be disabled via option 'git.autoRefresh'.
# window is closed. fetchInterval: 60 # Re-fetch interval in seconds. Auto-fetch can be disabled via option 'git.autoFetch'.
editAtLineAndWait: "" update:
method: prompt # can be: prompt | background | never
# For opening a directory in an editor days: 14 # how often an update is checked for
openDirInEditor: "" confirmOnQuit: false
# determines whether hitting 'esc' will quit the application when there is nothing to cancel/close
# A built-in preset that sets all of the above settings. Supported presets quitOnTopLevelReturn: false
# are defined in the getPreset function in editor_presets.go.
editPreset: ""
# Command for opening a file, as if the file is double-clicked. Should
# contain "{{filename}}", but doesn't support "{{line}}".
open: ""
# Command for opening a link. Should contain "{{link}}".
openLink: ""
# EditCommand is the command for editing a file.
# Deprecated: use Edit instead. Note that semantics are different:
# EditCommand is just the command itself, whereas Edit contains a
# "{{filename}}" variable.
editCommand: ""
# EditCommandTemplate is the command template for editing a file
# Deprecated: use EditAtLine instead.
editCommandTemplate: ""
# OpenCommand is the command for opening a file
# Deprecated: use Open instead.
openCommand: ""
# OpenLinkCommand is the command for opening a link
# Deprecated: use OpenLink instead.
openLinkCommand: ""
# CopyToClipboardCmd is the command for copying to clipboard.
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
copyToClipboardCmd: ""
# If true, don't display introductory popups upon opening Lazygit.
disableStartupPopups: false disableStartupPopups: false
notARepository: 'prompt' # one of: 'prompt' | 'create' | 'skip' | 'quit'
# What to do when opening Lazygit outside of a git repo. promptToReturnFromSubprocess: true # display confirmation when subprocess terminates
# - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
# - 'create': initialize a new repo
# - 'skip': open most recent repo
# - 'quit': exit Lazygit
notARepository: prompt
# If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
promptToReturnFromSubprocess: true
# Keybindings
keybinding: keybinding:
universal: universal:
quit: q quit: 'q'
quit-alt1: <c-c> quit-alt1: '<c-c>' # alternative/alias of quit
return: <esc> return: '<esc>' # return to previous menu, will quit if there's nowhere to return
quitWithoutChangingDirectory: Q quitWithoutChangingDirectory: 'Q'
togglePanel: <tab> togglePanel: '<tab>' # goto the next panel
prevItem: <up> prevItem: '<up>' # go one line up
nextItem: <down> nextItem: '<down>' # go one line down
prevItem-alt: k prevItem-alt: 'k' # go one line up
nextItem-alt: j nextItem-alt: 'j' # go one line down
prevPage: ',' prevPage: ',' # go to next page in list
nextPage: . nextPage: '.' # go to previous page in list
scrollLeft: H gotoTop: '<' # go to top of list
scrollRight: L gotoBottom: '>' # go to bottom of list
gotoTop: < scrollLeft: 'H' # scroll left within list view
gotoBottom: '>' scrollRight: 'L' # scroll right within list view
toggleRangeSelect: v prevBlock: '<left>' # goto the previous block / panel
rangeSelectDown: <s-down> nextBlock: '<right>' # goto the next block / panel
rangeSelectUp: <s-up> prevBlock-alt: 'h' # goto the previous block / panel
prevBlock: <left> nextBlock-alt: 'l' # goto the next block / panel
nextBlock: <right> jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
prevBlock-alt: h nextMatch: 'n'
nextBlock-alt: l prevMatch: 'N'
nextBlock-alt2: <tab> optionMenu: null # show help menu
prevBlock-alt2: <backtab> optionMenu-alt1: '?' # show help menu
jumpToBlock: select: '<space>'
- "1" goInto: '<enter>'
- "2" openRecentRepos: '<c-r>'
- "3" confirm: '<enter>'
- "4" remove: 'd'
- "5" new: 'n'
nextMatch: "n" edit: 'e'
prevMatch: "N" openFile: 'o'
startSearch: / scrollUpMain: '<pgup>' # main panel scroll up
optionMenu: <disabled> scrollDownMain: '<pgdown>' # main panel scroll down
optionMenu-alt1: '?' scrollUpMain-alt1: 'K' # main panel scroll up
select: <space> scrollDownMain-alt1: 'J' # main panel scroll down
goInto: <enter> scrollUpMain-alt2: '<c-u>' # main panel scroll up
confirm: <enter> scrollDownMain-alt2: '<c-d>' # main panel scroll down
confirmInEditor: <a-enter>
remove: d
new: "n"
edit: e
openFile: o
scrollUpMain: <pgup>
scrollDownMain: <pgdown>
scrollUpMain-alt1: K
scrollDownMain-alt1: J
scrollUpMain-alt2: <c-u>
scrollDownMain-alt2: <c-d>
executeCustomCommand: ':' executeCustomCommand: ':'
createRebaseOptionsMenu: m createRebaseOptionsMenu: 'm'
pushFiles: 'P'
# 'Files' appended for legacy reasons pullFiles: 'p'
pushFiles: P refresh: 'R'
createPatchOptionsMenu: '<c-p>'
# 'Files' appended for legacy reasons
pullFiles: p
refresh: R
createPatchOptionsMenu: <c-p>
nextTab: ']' nextTab: ']'
prevTab: '[' prevTab: '['
nextScreenMode: + nextScreenMode: '+'
prevScreenMode: _ prevScreenMode: '_'
undo: z undo: 'z'
redo: <c-z> redo: '<c-z>'
filteringMenu: <c-s> filteringMenu: '<c-s>'
diffingMenu: W diffingMenu: 'W'
diffingMenu-alt: <c-e> diffingMenu-alt: '<c-e>' # deprecated
copyToClipboard: <c-o> copyToClipboard: '<c-o>'
openRecentRepos: <c-r> submitEditorText: '<enter>'
submitEditorText: <enter>
extrasMenu: '@' extrasMenu: '@'
toggleWhitespaceInDiffView: <c-w> toggleWhitespaceInDiffView: '<c-w>'
increaseContextInDiffView: '}' increaseContextInDiffView: '}'
decreaseContextInDiffView: '{' decreaseContextInDiffView: '{'
openDiffTool: <c-t>
status: status:
checkForUpdate: u checkForUpdate: 'u'
recentRepos: <enter> recentRepos: '<enter>'
allBranchesLogGraph: a
files: files:
commitChanges: c commitChanges: 'c'
commitChangesWithoutHook: w commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
amendLastCommit: A amendLastCommit: 'A'
commitChangesWithEditor: C commitChangesWithEditor: 'C'
findBaseCommitForFixup: <c-f> ignoreFile: 'i'
confirmDiscard: x refreshFiles: 'r'
ignoreFile: i stashAllChanges: 's'
refreshFiles: r viewStashOptions: 'S'
stashAllChanges: s toggleStagedAll: 'a' # stage/unstage all
viewStashOptions: S viewResetOptions: 'D'
toggleStagedAll: a fetch: 'f'
viewResetOptions: D
fetch: f
toggleTreeView: '`' toggleTreeView: '`'
openMergeTool: M openMergeTool: 'M'
openStatusFilter: <c-b> openStatusFilter: '<c-b>'
copyFileInfoToClipboard: "y"
branches: branches:
createPullRequest: o createPullRequest: 'o'
viewPullRequestOptions: O viewPullRequestOptions: 'O'
copyPullRequestURL: <c-y> checkoutBranchByName: 'c'
checkoutBranchByName: c forceCheckoutBranch: 'F'
forceCheckoutBranch: F rebaseBranch: 'r'
rebaseBranch: r renameBranch: 'R'
renameBranch: R mergeIntoCurrentBranch: 'M'
mergeIntoCurrentBranch: M viewGitFlowOptions: 'i'
viewGitFlowOptions: i fastForward: 'f' # fast-forward this branch from its upstream
fastForward: f createTag: 'T'
createTag: T pushTag: 'P'
pushTag: P setUpstream: 'u' # set as upstream of checked-out branch
setUpstream: u fetchRemote: 'f'
fetchRemote: f
sortOrder: s
worktrees:
viewWorktreeOptions: w
commits: commits:
squashDown: s squashDown: 's'
renameCommit: r renameCommit: 'r'
renameCommitWithEditor: R renameCommitWithEditor: 'R'
viewResetOptions: g viewResetOptions: 'g'
markCommitAsFixup: f markCommitAsFixup: 'f'
createFixupCommit: F createFixupCommit: 'F' # create fixup commit for this commit
squashAboveCommits: S squashAboveCommits: 'S'
moveDownCommit: <c-j> moveDownCommit: '<c-j>' # move commit down one
moveUpCommit: <c-k> moveUpCommit: '<c-k>' # move commit up one
amendToCommit: A amendToCommit: 'A'
resetCommitAuthor: a pickCommit: 'p' # pick commit (when mid-rebase)
pickCommit: p revertCommit: 't'
revertCommit: t cherryPickCopy: 'c'
cherryPickCopy: C cherryPickCopyRange: 'C'
pasteCommits: V pasteCommits: 'v'
markCommitAsBaseForRebase: B tagCommit: 'T'
tagCommit: T checkoutCommit: '<space>'
checkoutCommit: <space> resetCherryPick: '<c-R>'
resetCherryPick: <c-R> copyCommitMessageToClipboard: '<c-y>'
copyCommitAttributeToClipboard: "y" openLogMenu: '<c-l>'
openLogMenu: <c-l> viewBisectOptions: 'b'
openInBrowser: o
viewBisectOptions: b
startInteractiveRebase: i
amendAttribute:
resetAuthor: a
setAuthor: A
addCoAuthor: c
stash: stash:
popStash: g popStash: 'g'
renameStash: r renameStash: 'r'
commitFiles: commitFiles:
checkoutCommitFile: c checkoutCommitFile: 'c'
main: main:
toggleSelectHunk: a toggleDragSelect: 'v'
pickBothHunks: b toggleDragSelect-alt: 'V'
editSelectHunk: E toggleSelectHunk: 'a'
pickBothHunks: 'b'
submodules: submodules:
init: i init: 'i'
update: u update: 'u'
bulkMenu: b bulkMenu: 'b'
commitMessage:
commitMenu: <c-o>
``` ```
<!-- END CONFIG YAML -->
## Platform Defaults ## Platform Defaults
@@ -611,36 +278,29 @@ os:
open: 'open {{filename}}' open: 'open {{filename}}'
``` ```
## Custom Command for Copying to Clipboard ### Configuring File Editing
```yaml
os:
copyToClipboardCmd: ''
```
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it: There are two commands for opening files, `o` for "open" and `e` for "edit". `o`
``` acts as if the file was double-clicked in the Finder/Explorer, so it also works
os: for non-text files, whereas `e` opens the file in an editor. `e` can also jump
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty to the right line in the file if you invoke it from the staging panel, for
``` example.
To tell lazygit which editor to use for the `e` command, the easiest way to do
## Configuring File Editing that is to provide an editPreset config, e.g.
There are two commands for opening files, `o` for "open" and `e` for "edit". `o` acts as if the file was double-clicked in the Finder/Explorer, so it also works for non-text files, whereas `e` opens the file in an editor. `e` can also jump to the right line in the file if you invoke it from the staging panel, for example.
To tell lazygit which editor to use for the `e` command, the easiest way to do that is to provide an editPreset config, e.g.
```yaml ```yaml
os: os:
editPreset: 'vscode' editPreset: 'vscode'
``` ```
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR. Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
`kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one. If for some reason you are not happy with the default commands from a preset, or
there simply is no preset for your editor, you can customize the commands by
If for some reason you are not happy with the default commands from a preset, or there simply is no preset for your editor, you can customize the commands by setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.: setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
```yaml ```yaml
os: os:
@@ -648,14 +308,15 @@ os:
editAtLine: 'myeditor --line={{line}} {{filename}}' editAtLine: 'myeditor --line={{line}} {{filename}}'
editAtLineAndWait: 'myeditor --block --line={{line}} {{filename}}' editAtLineAndWait: 'myeditor --block --line={{line}} {{filename}}'
editInTerminal: true editInTerminal: true
openDirInEditor: 'myeditor {{dir}}'
``` ```
The `editInTerminal` option is used to decide whether lazygit needs to suspend itself to the background before calling the editor. It should really be named `suspend` because for some cases like when lazygit is opened from within a neovim session and you're using the `nvim-remote` preset, you're technically still in a terminal. Nonetheless we're sticking with the name `editInTerminal` for backwards compatibility. The `editInTerminal` option is used to decide whether lazygit needs to suspend
itself to the background before calling the editor.
Contributions of new editor presets are welcome; see the `getPreset` function in [`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go). Contributions of new editor presets are welcome; see the `getPreset` function in
[`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
## Overriding default config file location ### Overriding default config file location
To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions. To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions.
@@ -669,20 +330,6 @@ or
LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit
``` ```
## Scroll-off Margin
When the selected line gets close to the bottom of the window and you hit down-arrow, there's a feature called "scroll-off margin" that lets the view scroll a little earlier so that you can see a bit of what's coming in the direction that you are moving. This is controlled by the `gui.scrollOffMargin` setting (default: 2), so it keeps 2 lines below the selection visible as you scroll down. It can be set to 0 to scroll only when the selection reaches the bottom of the window.
That's the behavior when `gui.scrollOffBehavior` is set to "margin" (the default). If you set `gui.scrollOffBehavior` to "jump", then upon reaching the last line of a view and hitting down-arrow the view will scroll by half a page so that the selection ends up in the middle of the view. This may feel a little jarring because the cursor jumps around when continuously moving down, but it has the advantage that the view doesn't scroll as often.
This setting applies both to all list views (e.g. commits and branches etc), and to the staging view.
## Filtering
We have two ways to filter things, substring matching (the default) and fuzzy searching. With substring matching, the text you enter gets searched for verbatim (usually case-insensitive, except when your filter string contains uppercase letters, in which case we search case-sensitively). You can search for multiple non-contiguous substrings by separating them with spaces; for example, "int test" will match "integration-testing". All substrings have to match, but not necessarily in the given order.
Fuzzy searching is smarter in that it allows every letter of the filter string to match anywhere in the text (only in order though), assigning a weight to the quality of the match and sorting by that order. This has the advantage that it allows typing "clt" to match "commit_loader_test" (letters at the beginning of subwords get more weight); but it has the disadvantage that it tends to return lots of irrelevant results, especially with short filter strings.
## Color Attributes ## Color Attributes
For color attributes you can choose an array of attributes (with max one color attribute) For color attributes you can choose an array of attributes (with max one color attribute)
@@ -710,13 +357,15 @@ The available attributes are:
## Highlighting the selected line ## Highlighting the selected line
If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` key to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following: If you don't like the default behaviour of highlighting the selected line with a blue background, you can use the `selectedLineBgColor` and `selectedRangeBgColor` keys to customise the behaviour. If you just want to embolden the selected line (this was the original default), you can do the following:
```yaml ```yaml
gui: gui:
theme: theme:
selectedLineBgColor: selectedLineBgColor:
- default - default
selectedRangeBgColor:
- default
``` ```
You can also use the reverse attribute like so: You can also use the reverse attribute like so:
@@ -726,6 +375,8 @@ gui:
theme: theme:
selectedLineBgColor: selectedLineBgColor:
- reverse - reverse
selectedRangeBgColor:
- reverse
``` ```
## Custom Author Color ## Custom Author Color
@@ -775,18 +426,19 @@ gui:
nerdFontsVersion: "3" nerdFontsVersion: "3"
``` ```
Supported versions are "2" and "3". The deprecated config `showIcons` sets the version to "2" for backwards compatibility. Supported versions are "2" and "3". The deprecated config `showIcons` sets the
version to "2" for backwards compatibility.
## Keybindings ## Keybindings
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
You can disable certain key bindings by specifying `<disabled>`. You can disable certain key bindings by specifying `null`.
```yaml ```yaml
keybinding: keybinding:
universal: universal:
edit: <disabled> # disable 'edit file' edit: null # disable 'edit file'
``` ```
### Example Keybindings For Colemak Users ### Example Keybindings For Colemak Users
@@ -823,7 +475,9 @@ keybinding:
## Custom pull request URLs ## Custom pull request URLs
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and the web interface/API itself. To work with those, Lazygit needs to know where it needs to create the pull request. You can do so on your `config.yml` file using the following syntax: Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and
the web interface/API itself. To work with those, Lazygit needs to know where it needs to create
the pull request. You can do so on your `config.yml` file using the following syntax:
```yaml ```yaml
services: services:
@@ -838,22 +492,14 @@ Where:
## Predefined commit message prefix ## 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. 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.
Example: Example:
- Branch name: feature/AB-123 - Branch name: feature/AB-123
- Commit message: [AB-123] Adding feature - Commit message: [AB-123] Adding feature
```yaml
git:
commitPrefix:
pattern: "^\\w+\\/(\\w+-\\w+).*"
replace: '[$1] '
```
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both `commitPrefixes` defined and an entry in `commitPrefixes` for the current repo, the `commitPrefixes` entry is given higher precedence. Repository folder names must be an exact match.
```yaml ```yaml
git: git:
commitPrefixes: commitPrefixes:
@@ -877,7 +523,9 @@ Result:
## Launching not in a repository behaviour ## Launching not in a repository behaviour
By default, when launching lazygit from a directory that is not a repository, you will be prompted to choose if you would like to initialize a repo. You can override this behaviour in the config with one of the following: By default, when launching lazygit from a directory that is not a repository,
you will be prompted to choose if you would like to initialize a repo. You can
override this behaviour in the config with one of the following:
```yaml ```yaml
# for default prompting behaviour # for default prompting behaviour
+2 -5
View File
@@ -6,7 +6,7 @@ You can add custom command keybindings in your config.yml (accessible by pressin
customCommands: customCommands:
- key: '<c-r>' - key: '<c-r>'
context: 'commits' context: 'commits'
command: 'hub browse -- "commit/{{.SelectedLocalCommit.Hash}}"' command: 'hub browse -- "commit/{{.SelectedLocalCommit.Sha}}"'
- key: 'a' - key: 'a'
context: 'files' context: 'files'
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}" command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}"
@@ -59,7 +59,6 @@ For a given custom command, here are the allowed fields:
| description | Label for the custom command when displayed in the keybindings menu | no | | description | Label for the custom command when displayed in the keybindings menu | no |
| stream | Whether you want to stream the command's output to the Command Log panel | no | | stream | Whether you want to stream the command's output to the Command Log panel | no |
| showOutput | Whether you want to show the command's output in a popup within Lazygit | no | | showOutput | Whether you want to show the command's output in a popup within Lazygit | no |
| outputTitle | The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title. | no |
| after | Actions to take after the command has completed | no | | after | Actions to take after the command has completed | no |
Here are the options for the `after` key: Here are the options for the `after` key:
@@ -75,7 +74,6 @@ The permitted contexts are:
| -------------- | -------------------------------------------------------------------------------------------------------- | | -------------- | -------------------------------------------------------------------------------------------------------- |
| status | The 'Status' tab | | status | The 'Status' tab |
| files | The 'Files' tab | | files | The 'Files' tab |
| worktrees | The 'Worktrees' tab |
| localBranches | The 'Local Branches' tab | | localBranches | The 'Local Branches' tab |
| remotes | The 'Remotes' tab | | remotes | The 'Remotes' tab |
| remoteBranches | The context you get when pressing enter on a remote in the remotes tab | | remoteBranches | The context you get when pressing enter on a remote in the remotes tab |
@@ -302,11 +300,10 @@ SelectedRemote
SelectedTag SelectedTag
SelectedStashEntry SelectedStashEntry
SelectedCommitFile SelectedCommitFile
SelectedWorktree
CheckedOutBranch CheckedOutBranch
``` ```
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file). To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit Lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Sha}}` and `{{.SelectedLocalBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
## Keybinding collisions ## Keybinding collisions
+1 -23
View File
@@ -1,6 +1,6 @@
# Custom Pagers # 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). Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `o` 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. Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support.
@@ -62,25 +62,3 @@ git:
``` ```
If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager). If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
## 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:
paging:
externalDiffCommand: difft --color=always
```
The `colorArg`, `pager`, and `useConfig` options are not used in this case.
You can add whatever extra arguments you prefer for your difftool; for instance
```yaml
git:
paging:
externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
```
-65
View File
@@ -1,65 +0,0 @@
# Fixup Commits
## Background
There's this common scenario that you have a PR in review, the reviewer is
requesting some changes, and you make those changes and would normally simply
squash them into the original commit that they came from. If you do that,
however, there's no way for the reviewer to see what you changed. You could just
make a separate commit with those changes at the end of the branch, but this is
not ideal because it results in a git history that is not very clean.
To help with this, git has a concept of fixup commits: you do make a separate
commit, but the subject of this commit is the string "fixup! " followed by the
original commit subject. This both tells the reviewer what's going on (you are
making a change that you later will squash into the designated commit), and it
provides an easy way to actually perform this squash operation when you are
ready to do that (before merging).
## Creating fixup commits
You could of course create fixup commits manually by typing in the commit
message with the prefix yourself. But lazygit has an easier way to do that:
in the Commits view, select the commit that you want to create a fixup for, and
press shift-F (for "Create fixup commit for this commit"). This automatically
creates a commit with the appropriate subject line.
Don't confuse this with the lowercase "f" command ("Fixup commit"); that one
squashes the selected commit into its parent, this is not what we want here.
## Creating amend commits
There's a special type of fixup commit that uses "amend!" instead of "fixup!" in
the commit message subject; in addition to fixing up the original commit with
changes it allows you to also (or only) change the commit message of the
original commit. The menu that appears when pressing shift-F has options for
both of these; they bring up a commit message panel similar to when you reword a
commit, but then create the "amend!" commit containing the new message. Note
that in that panel you only type the new message as you want it to be
eventually; lazygit then takes care of formatting the "amend!" commit
appropriately for you (with the subject of your new message moving into the body
of the "amend!" commit).
## Squashing fixup commits
When you're ready to merge the branch and want to squash all these fixup commits
that you created, that's very easy to do: select the first commit of your branch
and hit shift-S (for "Squash all 'fixup!' commits above selected commit
(autosquash)"). Boom, done.
## Finding the commit to create a fixup for
When you are making changes to code that you changed earlier in a long branch,
it can be tedious to find the commit to squash it into. Lazygit has a command to
help you with this, too: in the Files view, press ctrl-f to select the right
base commit in the Commits view automatically. From there, you can either press
shift-F to create a fixup commit for it, or shift-A to amend your changes into
the commit if you haven't published your branch yet.
If you have many modifications in your working copy, it is a good idea to stage
related changes that are meant to go into the same fixup commit; if no changes
are staged, ctrl-f works on all unstaged modifications, and then it might show
an error if it finds multiple different base commits. If you are interested in
what the command does to do its magic, and how you can help it work better, you
may want to read the [design document](dev/Find_Base_Commit_For_Fixup_Design.md)
that describes this.
+1 -3
View File
@@ -3,9 +3,7 @@
* [Configuration](./Config.md). * [Configuration](./Config.md).
* [Custom Commands](./Custom_Command_Keybindings.md) * [Custom Commands](./Custom_Command_Keybindings.md)
* [Custom Pagers](./Custom_Pagers.md) * [Custom Pagers](./Custom_Pagers.md)
* [Dev docs](./dev)
* [Keybindings](./keybindings) * [Keybindings](./keybindings)
* [Undo/Redo](./Undoing.md) * [Undo/Redo](./Undoing.md)
* [Range Select](./Range_Select.md)
* [Searching/Filtering](./Searching.md) * [Searching/Filtering](./Searching.md)
* [Stacked Branches](./Stacked_Branches.md) * [Dev docs](./dev)
-14
View File
@@ -1,14 +0,0 @@
# Range Select
Some actions can be performed on a range of contiguous items. For example:
* staging multiple files at once
* squashing multiple commits at once
* copying (for cherry-pick) multiple commits at once
There are two ways to select a range of items:
1. Sticky range select: Press 'v' to toggle range select, then expand the selection using the up/down arrow key. To reset the selection, press 'v' again.
2. Non-sticky range select: Press shift+up or shift+down to expand the selection. To reset the selection, press up/down without shift.
The sticky option will be more familiar to vim users, and the second option will feel more natural to users who aren't used to doing things in a modal way.
In order to perform an action on a range of items, simply press the normal key for that action. If the action only works on individual items, it will raise an error. This is a new feature and the plan is to incrementally support range select for more and more actions. If there is an action you would like to support range select which currently does not, please raise an issue in the repo.
-18
View File
@@ -1,18 +0,0 @@
# Working with stacked branches
When working on a large branch it can often be useful to break it down into
smaller pieces, and it can help to create separate branches for each independent
chunk of changes. For example, you could have one branch for preparatory
refactorings, one for backend changes, and one for frontend changes. Those
branches would then all be stacked onto each other.
Git has support for rebasing such a stack as a whole; you can enable it by
setting the git config `rebase.updateRefs` to true. If you then rebase the
topmost branch of the stack, the other ones in the stack will follow. This
includes interactive rebases, so for example amending a commit in the first
branch of the stack will "just work" in the sense that it keeps the other
branches properly stacked onto it.
Lazygit visualizes the individual branch heads in the stack by marking them with a
cyan asterisk (or a cyan branch symbol if you are using [nerd
fonts](Config.md#display-nerd-fonts-icons)).
+3 -3
View File
@@ -1,9 +1,9 @@
# Undo/Redo in lazygit # 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. ![Gif](../../assets/undo2.gif)
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
![undo](../../assets/demo/undo-compressed.gif) ## Keybindings:
'z' to undo, 'ctrl+z' to redo
## How it works ## How it works
+1 -1
View File
@@ -57,7 +57,7 @@ go utils.Safe(f)
Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics. Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics.
### Programmatically enqueueing a UI event ### Programmatically enqueing a UI event
This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`. This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`.
-94
View File
@@ -1,94 +0,0 @@
# Lazygit Codebase Guide
## Packages
* `pkg/app`: Contains startup code, inititalises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase.
* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`.
* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`.
* `pkg/commands/oscommands`: Contains code for talking to the OS, and for invoking commands in general
* `pkg/commands/git_config`: Reading of the git config all happens here.
* `pkg/commands/hosting_service`: Contains code that is specific to git hosting services (aka forges).
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
* `pkg/commands/patch`: Contains code for parsing and working with git patches
* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
* `pkg/constants`: Contains some constant strings (e.g. links to docs)
* `pkg/env`: Contains code relating to setting/getting environment variables
* `pkg/i18n`: Contains internationalised strings
* `pkg/integration`: Contains end-to-end tests
* `pkg/jsonschema`: Contains generator for user config JSON schema.
* `pkg/logs`: Contains code for instantiating the logger and for tailing the logs via `lazygit --logs`
* `pkg/tasks`: Contains code for running asynchronous tasks: mostly related to efficiently rendering command output to the main window.
* `pkg/theme`: Contains code related to colour themes.
* `pkg/updates`: Contains code related to Lazygit updates (checking for update, download and installing the update)
* `pkg/utils`: Contains lots of low-level helper functions
* `pkg/gui`: Contains code related to the gui. We've still got a God Struct in the form of our Gui struct, but over time code has been moved out into contexts, controllers, and helpers, and we intend to continue moving more code out over time.
* `pkg/gui/context`: Contains code relating to contexts. There is a context for each view e.g. a branches context, a tags context, etc. Contexts manage state related to the view and receive keypresses.
* `pkg/gui/controllers`: Contains code relating to controllers. Controllers define a list of keybindings and their associated handlers. One controller can be assigned to multiple contexts, and one context can contain multiple controllers.
* `pkg/gui/controllers/helpers`: Contains code that is shared between multiple controllers.
* `pkg/gui/filetree`: Contains code relating to the representation of filetrees.
* `pkg/gui/keybindings`: Contains code for mapping between keybindings and their labels
* `pkg/gui/mergeconflicts`: Contains code relating to the handling of merge conflicts
* `pkg/gui/modes`: Contains code relating to the state of different modes e.g. cherry picking mode, rebase mode.
* `pkg/gui/patch_exploring`: Contains code relating to the state of patch-oriented views like the staging view.
* `pkg/gui/popup`: Contains code that lets you easily raise popups
* `pkg/gui/presentation`: Contains presentation code i.e. code concerned with rendering content inside views
* `pkg/gui/services/custom_commands`: Contains code related to user-defined custom commands.
* `pkg/gui/status`: Contains code for invoking loaders and toasts
* `pkg/gui/style`: Contains code for specifying text styles (colour, bold, etc)
* `pkg/gui/types`: Contains various gui-specific types and interfaces. Lots of code lives here to avoid circular dependencies
* `vendor/github.com/jesseduffield/gocui`: Gocui is the underlying library used for handling the gui event loop, handling keypresses, and rendering the UI. It defines the View struct which our own context structs build upon.
## Important files
* `pkg/config/user_config.go`: defines the user config and default values
* `pkg/gui/keybindings.go`: defines keybindings which have not yet been moved into a controller (originally all keybindings were defined here)
* `pkg/gui/controllers.go`: links up controllers with contexts
* `pkg/gui/controllers/helpers/helpers.go`: defines all the different helper structs
* `pkg/commands/git.go`: defines all the different git command structs
* `pkg/gui/gui.go`: defines the top-level gui state and gui initialisation/run code
* `pkg/gui/layout.go`: defines what happens on each render
* `pkg/gui/controllers/helpers/window_arrangement_helper.go`: defines the layout of the UI and the size/position of each window
* `pkg/gui/context/context.go`: defines the different contexts
* `pkg/gui/context/setup.go`: defines initialisation code for all contexts
* `pkg/gui/context.go`: manages the lifecycle of contexts, the context stack, and focus changes.
* `pkg/gui/types/views.go`: defines views
* `pkg/gui/views.go`: defines the ordering of views (front to back) and their initialisation code
* `pkg/gui/gui_common.go`: defines gui-specific methods that all controllers and helpers have access to
* `pkg/i18n/english.go`: defines the set of i18n strings and their English values
* `pkg/gui/controllers/helpers/refresh_helper.go`: manages refreshing of models. The refresh helper is typically invoked at the end of an action to re-load affected models from git (e.g. re-load branches after doing a git pull)
* `pkg/gui/controllers/quit_actions.go`: contains code that runs when you hit 'escape' on a view (assuming the view doesn't define its own escape handler)
* `vendor/github.com/jesseduffield/gocui/gui.go`: defines the gocui gui struct
* `vendor/github.com/jesseduffield/gocui/view.go`: defines the gocui view struct
## Concepts
* **View**: Views are defined in the gocui package, and they maintain an internal buffer of content which is rendered each time the screen is drawn.
* **Context**: A context is tied to a view and contains some additional state and logic specific to that view e.g. the branches context has code relating specifically to branches, and writes the list of branches to the branches view. Views and contexts share some responsibilities for historical reasons.
* **Controller**: A controller defined keybindings with associated handlers. One controller can be assigned to multiple contexts and one context can have multiple controllers. For example the list controller handles keybindings relating to navigating a list, and is assigned to all list contexts (e.g. the branches context).
* **Helper**: A helper defines shared code used by controllers, or used by some other parts of the application. Often a controller will have a method that ends up needing to be used by another controller, so in that case we move the method out into a helper so that both controllers can use it. We need to do this because controllers cannot refer to other controllers' methods.
In terms of dependencies, controllers sit at the highest level, so they can refer to helpers, contexts, and views (although it's preferable for view-specific code to live in contexts). Helpers can refer to contexts and views, and contexts can only refer to views. Views can't refer to contexts, controllers, or helpers.
* **Window**: A window is a section of the screen which will render a view. Windows are named after the default view that appears there, so for example there is a 'stash' window that is so named because by default the stash view appears there. But if you press enter on a stash entry, the stash entry's files will be shown in a different view, but in the same window.
* **Panel**: The term 'panel' is still used in a few places to refer to either a view or a window, and it's a term that is now deprecated in favour of 'view' and 'window'.
* **Tab**: Each tab in a window (e.g. Files, Worktrees, Submodules) actually has a corresponding view which we bring to the front upon changing tabs.
* **Model**: Representation of a git object e.g. commits, branches, files.
* **ViewModel**: Used by a context to maintain state related to the view.
* **Keybinding**: A keybinding associates a _key_ with an _action_. For example if you press the 'down' arrow, the action performed will be your cursor moving down a list by one.
* **Action**: An action is the thing that happens when you press a key. Often an action will invoke a git command, but not always: for example, navigation actions don't involve git.
* **Common structs**: Most structs have a field named `c` which contains a 'common' struct: a struct containing a bag of dependencies that most structs of the same layer require. For example if you want to access a helper from a controller you can do so with `self.c.Helpers.MyHelper`.
## Event loop and threads
The event loop is managed in the `MainLoop` function of `vendor/github.com/jesseduffield/gocui/gui.go`. Any time there is an event like a key press or a window resize, the event will be processed and then the screen will be redrawn. This involves calling the `layout` function defined in `pkg/gui/layout.go`, which lays out the windows and invokes some on-render hooks.
Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
## Legacy code structure
Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).
The new structure has its own problems: we don't have a clear guide on whether code should live in a controller or helper. The current approach is to put code in a controller until it's needed by another controller, and to then extract it out into a helper. We may be better off just putting code in helpers to start with and leaving controllers super-thin, with the responsibility of just pairing keys with corresponding helper functions. But it's not clear to me if that would be better than the current approach.
-82
View File
@@ -1,82 +0,0 @@
# Demo Recordings
We want our demo recordings to be consistent and easy to update if we make changes to Lazygit's UI. Luckily for us, we have an existing recording system for the sake of our integration tests, so we can piggyback on that.
You'll want to familiarise yourself with how integration tests are written: see [here](../../pkg/integration/README.md).
## Prerequisites
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
```
# for recording
npm i -g terminalizer
# for gif compression
npm i -g gifsicle
# for mp4 conversion
brew install ffmpeg
# font with icons
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
rm DejaVuSansMono.tar.xz
```
## Creating a demo
Demos are found in `pkg/integration/tests/demo/`. They are like regular integration tests but have `IsDemo: true` which has a few effects:
* The bottom row of the UI is quieter so that we can render captions
* Fetch/Push/Pull have artificial latency to mimic a network request
* The loader at the bottom-right does not appear
In demos, we don't need to be as strict in our assertions as we are in tests. But it's still good to have some basic assertions so that if we automate the process of updating demos we'll know if one of them has broken.
You can use the same flow as we use with integration tests when you're writing a demo:
* Setup the repo
* Run the demo in sandbox mode to get a feel of what needs to happen
* Come back and write the code to make it happen
### Adding captions
It's good to add captions explaining what task if being performed. Use the existing demos as a guide.
### Setting up the assets worktree
We store assets (which includes demo recordings) in the `assets` branch, which is a branch that shares no history with the main branch and exists purely for storing assets. Storing them separately means we don't clog up the code branches with large binaries.
The scripts and demo definitions live in the code branches but the output lives in the assets branch so to be able to create a video from a demo you'll need to create a linked worktree for the assets branch which you can do with:
```sh
git worktree add .worktrees/assets assets
```
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
* the yaml of the recording
* the original gif
* either the compressed gif or the mp4 depending on the output you chose (see below)
### Recording the demo
Once you're happy with your demo you can record it using:
```sh
scripts/record_demo.sh [gif|mp4] <path>
# e.g.
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
```
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
### Including demos in README/docs
If you've followed the above steps you'll end up with your output in your assets worktree.
Within that worktree, stage all three output files and raise a PR against the assets branch.
Then back in the code branch, in the doc, you can embed the recording like so:
```md
![Nuke working tree](../assets/demo/interactive_rebase-compressed.gif)
```
This means we can update assets without needing to update the docs that embed them.
@@ -1,229 +0,0 @@
# About the mechanics of lazygit's "Find base commit for fixup" command
## Background
Lazygit has a command called "Find base commit for fixup" that helps with
creating fixup commits. (It is bound to "ctrl-f" by default, and I'll call it
simply "the ctrl-f command" throughout the rest of this text for brevity.)
It's a heuristic that needs to make a few assumptions; it tends to work well in
practice if users are aware of its limitations. The user-facing side of the
topic is explained [here](../Fixup_Commits.md). In this document we describe how
it works internally, and the design decisions behind it.
It is also interesting to compare it to the standalone tool
[git-absorb](https://github.com/tummychow/git-absorb) which does a very similar
thing, but made different decisions in some cases. We'll explore these
differences in this document.
## Design goals
I'll start with git-absorb's design goals (my interpretation, since I can't
speak for git-absorb's maintainer of course): its main goal seems to be minimum
user interaction required. The idea is that you have a PR in review, the
reviewer requested a bunch of changes, you make all these changes, so you have a
working copy with lots of modified files, and then you fire up git-absorb and it
creates all the necessary fixup commits automatically with no further user
intervention.
While this sounds attractive, it conflicts with ctrl-f's main design goal, which
is to support creating high-quality fixups. My philosophy is that fixup commits
should have the same high quality standards as normal commits; in particular:
- they should be atomic. This means that multiple diff hunks that belong
together to form one logical change should be in the same fixup commit. (Not
always possible if the logical change needs to be fixed up into several
different base commits.)
- they should be minimal. Every fixup commit should ideally contain only one
logical change, not several unrelated ones.
Why is this important? Because fixup commits are mainly a tool for reviewing (if
they weren't, you might as well squash the changes into their base commits right
away). And reviewing fixup commits is easier if they are well-structured, just
like normal commits.
The only way to achieve this with git-absorb is to set the `oneFixupPerCommit`
config option (for the first goal), and then manually stage the changes that
belong together (for the second). This is close to what you have to do with
ctrl-f, with one exception that we'll get to below.
But ctrl-f enforces this by refusing to do the job if the staged hunks belong to
more than one base commit. Git-absorb will happily create multiple fixup commits
in this case; ctrl-f doesn't, to enforce that you pay attention to how you group
the changes. There's another reason for this behavior: ctrl-f doesn't create
fixup commits itself (unlike git-absorb), instead it just selects the found base
commit so that the user can decide whether to amend the changes right in, or
create a fixup commit from there (both are single-key commands in lazygit). And
lazygit doesn't support non-contiguous multiselections of commits, but even if
it did, it wouldn't help much in this case.
## The mechanics
### General approach
Git-absorb uses a relatively simple approach, and the benefit is of course that
it is easy to understand: it looks at every diff hunk separately, and for every
hunk it looks at all commits (starting from the newest one backwards) to find
the earliest commit that the change can be amended to without conflicts.
It is important to realize that "diff hunk" doesn't necessarily mean what you
see in the diff view. Git-absorb and ctrl-f both use a context of 0 when diffing
your code, so they often see more and smaller hunks than users do. For example,
moving a line of code down by one line is a single hunk for users, but it's two
separate hunks for git-absorb and ctrl-f; one for deleting the line at the old
place, and another one for adding the line at the new place, even if it's only
one line further down.
From this, it follows that there's one big problem with git-absorb's approach:
when moving code, it doesn't realize that the two related hunks of deleting the
code from the old place and inserting it at the new place belong together, and
often it will manage to create a fixup commit for the first hunk, but leave the
other hunk in your working copy as "don't know what to do with this". As an
example, suppose your PR is adding a line of code to an existing function, maybe
one that declares a new variable, and a reviewer suggests to move this line down
a bit, closer to where some other related variables are declared. Moving the
line down results in two diff hunks (from the perspective of git-absorb and
ctrl-f, as they both use a context of 0 when diffing), and when looking at the
second diff hunk in isolation there's no way to find a base commit in your PR
for it, because the surrounding code is already on main.
To solve this, the ctrl-f command makes a distinction between hunks that have
deleted lines and hunks that have only added lines. If the whole diff contains
any hunks that have deleted lines, it uses only those hunks to determine the
base commit, and then assumes that all the hunks that have only added lines
belong into the same commit. This nicely solves the above example of moving
code, but also other examples such as the following:
<details>
<summary>Click to show example</summary>
Suppose you have a PR in which you added the following function:
```go
func findCommit(hash string) (*models.Commit, int, bool) {
for i, commit := range self.c.Model().Commits {
if commit.Hash == hash {
return commit, i, true
}
}
return nil, -1, false
}
```
A reviewer suggests to replace the manual `for` loop with a call to
`lo.FindIndexOf` since that's less code and more idiomatic. So your modification
is this:
```diff
--- a/my_file.go
+++ b/my_file.go
@@ -12,2 +12,3 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/samber/lo"
"golang.org/x/sync/errgroup"
@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, erro
func findCommit(hash string) (*models.Commit, int, bool) {
- for i, commit := range self.c.Model().Commits {
- if commit.Hash == hash {
- return commit, i, true
- }
- }
-
- return nil, -1, false
+ return lo.FindIndexOf(self.c.Model().Commits, func(commit *models.Commit) bool {
+ return commit.Hash == hash
+ })
}
```
If we were to look at these two hunks separately, we'd easily find the base
commit for the second one, but we wouldn't find the one for the first hunk
because the imports around the added import have been on main for a long time.
In fact, git-absorb leaves this hunk in the working copy because it doesn't know
what to do with it.
</details>
Only if there are no hunks with deleted lines does ctrl-f look at the hunks with
only added lines and determines the base commit for them. This solves cases like
adding a comment above a function that you added in your PR.
The downside of this more complicated approach is that it relies on the user
staging related hunks correctly. However, in my experience this is easy to do
and not very error-prone, as long as users are aware of this behavior. Lazygit
tries to help making them aware of it by showing a warning whenever there are
hunks with only added lines in addition to hunks with deleted lines.
### Finding the base commit for a given hunk
As explained above, git-absorb finds the base commit by walking the commits
backwards until it finds one that conflicts with the hunk, and then the found
base commit is the one just before that one. This works reliably, but it is
slow.
Ctrl-f uses a different approach that is usually much faster, but should always
yield the same result. Again, it makes a distinction between hunks with deleted
lines and hunks with only added lines. For hunks with deleted lines it performs
a line range blame for all the deleted lines (e.g. `git blame -L42,+3 --
filename`), and if the result is the same for all deleted lines, then that's the
base commit; otherwise it returns an error.
For hunks with only added lines, it gets a little more complicated. We blame the
single lines just before and just after the hunk (I'll ignore the edge cases of
either of those not existing because the hunk is at the beginning or end of the
file; read the code to see how we handle these cases). If the blame result is
the same for both, then that's the base commit. This is the case of adding a
line in the middle of a block of code that was added in the PR. Otherwise, the
base commit is the more recent of the two (and in this case it doesn't matter if
the other one is an earlier commit in the current branch, or a possibly very old
commit that's already on main). This covers the common case of adding a comment
to a function that was added in the PR, but also adding another line at the end
of a block of code that was added in the base commit.
It's interesting to discuss what "more recent" means here. You could say if
commit A is an ancestor of commit B (or in other words, A is reachable from B)
then B is the more recent one. And if none of the two commits is reachable from
the other, you have an error case because it's unclear which of the two should
be considered the base commit. The scenario in which this happens is a commit
history like this:
```
C---D
/ \
A---B---E---F---G
```
where, for instance, D and E are the two blame results.
Unfortunately, determining the ancestry relationship between two commits using
git commands is a bit expensive and not totally straightforward. Fortunately,
it's not necessary in lazygit because lazygit has the most recent 300 commits
cached in memory, and can simply search its linear list of commits to see which
one is closer to the beginning of the list. If only one of the two commits is
found within those 300 commits, then that's the more recent one; if neither is
found, we assume that both commits are on main and error out. In the merge
scenario pictured above, we arbitrarily return one of the two commits (this will
depend on the log order), but that's probably fine as this scenario should be
extremely rare in practice; in most cases, feature branches are simply linear.
### Knowing where to stop searching
Git-absorb needs to know when to stop walking backwards searching for commits,
since it doesn't make sense to create fixups for commits that are already on
main. However, it doesn't know where the current branch ends and main starts, so
it needs to rely on user input for this. By default it searches the most recent
10 commits, but this can be overridden with a config setting. In longer branches
this is often not enough for finding the base commit; but setting it to a higher
value causes the command to take longer to complete when the base commit can't
be found.
Lazygit doesn't have this problem. For a given blame result it needs to
determine whether that commit is already on main, and if it can find the commit
in its cached list of the first 300 commits it can get that information from
there, because lazygit knows what the user's configured main branches are
(`master` and `main` by default, but it could also include branches like `devel`
or `1.0-hotfixes`), and so it can tell for each commit whether it's contained in
one of those main branches. And if it can't find it among the first 300 commits,
it assumes the commit already on main, on the assumption that no feature branch
has more than 300 commits.
-69
View File
@@ -1,69 +0,0 @@
# Profiling Lazygit
If you want to investigate what's contributing to CPU or memory usage, start
lazygit with the `-profile` command line flag. This tells it to start an
integrated web server that listens for profiling requests.
## Save profile data
### CPU
While lazygit is running with the `-profile` flag, perform a CPU profile and
save it to a file by running this command in another terminal window:
```sh
curl -o cpu.out http://127.0.0.1:6060/debug/pprof/profile
```
By default, it profiles for 30 seconds. To change the duration, use
```sh
curl -o cpu.out 'http://127.0.0.1:6060/debug/pprof/profile?seconds=60'
```
### Memory
To save a heap profile (containing information about all memory allocated so
far since startup), use
```sh
curl -o mem.out http://127.0.0.1:6060/debug/pprof/heap
```
Sometimes it can be useful to get a delta log, i.e. to see how memory usage
developed from one point in time to another. For that, use
```sh
curl -o mem.out 'http://127.0.0.1:6060/debug/pprof/heap?seconds=20'
```
This will log the memory usage difference between now and 20 seconds later, so
it gives you 20 seconds to perform the action in lazygit that you are interested
in measuring.
## View profile data
To display the profile data, you can either use speedscope.app, or the pprof
tool that comes with go. I prefer the former because it has a nicer UI and is a
little more powerful; however, I have seen cases where it wasn't able to load a
profile for some reason, in which case it's good to have the pprof tool as a
fallback.
### Speedscope.app
Go to https://www.speedscope.app/ in your browser, and drag the saved profile
onto the browser window. Refer to [the
documentation](https://github.com/jlfwong/speedscope?tab=readme-ov-file#usage)
for how to navigate the data.
### Pprof tool
To view a profile that you saved as `cpu.out`, use
```sh
go tool pprof -http=:8080 cpu.out
```
By default this shows the graph view, which I don't find very useful myself.
Choose "Flame Graph" from the View menu to show a much more useful
representation of the data.
+1 -5
View File
@@ -1,8 +1,4 @@
# Dev Documentation Overview # Dev Documentation Overview
* [Codebase Guide](./Codebase_Guide.md) * [Busy/Idle tracking](./Busy.md).
* [Busy/Idle Tracking](./Busy.md)
* [Integration Tests](../../pkg/integration/README.md) * [Integration Tests](../../pkg/integration/README.md)
* [Demo Recordings](./Demo_Recordings.md)
* [Find base commit for fixup design](Find_Base_Commit_For_Fixup_Design.md)
* [Profiling](Profiling.md)
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Keybindings # Lazygit Keybindings
@@ -6,359 +6,328 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Global keybindings ## Global keybindings
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: Switch to a recent repo
| `` <c-r> `` | Switch to a recent repo | | <kbd>&lt;pgup&gt;</kbd>: Scroll up main panel (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | Scroll up main window | | <kbd>&lt;pgdown&gt;</kbd>: Scroll down main panel (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | Scroll down main window | | <kbd>@</kbd>: Open command log menu
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
| `` 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. | <kbd>:</kbd>: Execute custom command
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: View custom patch options
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: View merge/rebase options
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: Refresh
| `` <c-p> `` | View custom patch options | | <kbd>+</kbd>: Next screen mode (normal/half/fullscreen)
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: Prev screen mode
| `` 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`. | <kbd>?</kbd>: Open menu
| `` + `` | Next screen mode (normal/half/fullscreen) | | <kbd>&lt;c-s&gt;</kbd>: View filter-by-path options
| `` _ `` | Prev screen mode | | <kbd>W</kbd>: Open diff menu
| `` ? `` | Open keybindings menu | | <kbd>&lt;c-e&gt;</kbd>: Open diff menu
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
| `` 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. | <kbd>z</kbd>: Undo
| `` <c-e> `` | 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. | <kbd>&lt;c-z&gt;</kbd>: Redo
| `` q `` | Quit | | <kbd>P</kbd>: Push
| `` <esc> `` | Cancel | | <kbd>p</kbd>: Pull
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` 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. |
| `` <c-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. |
## List panel navigation ## List panel navigation
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: Previous page
| `` , `` | Previous page | | <kbd>.</kbd>: Next page
| `` . `` | Next page | | <kbd>&lt;</kbd>: Scroll to top
| `` < `` | Scroll to top | | <kbd>&gt;</kbd>: Scroll to bottom
| `` > `` | Scroll to bottom | | <kbd>/</kbd>: Search the current view by text
| `` v `` | Toggle range select | | <kbd>H</kbd>: Scroll left
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: Scroll right
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: Next tab
| `` / `` | Search the current view by text | | <kbd>[</kbd>: Previous tab
| `` H `` | Scroll left | | </pre>
| `` L `` | Scroll right | |
| `` ] `` | Next tab | |
| `` [ `` | Previous tab | |
## Commit files ## Commit files
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy the committed file name to the clipboard
| `` <c-o> `` | Copy path to clipboard | | <kbd>c</kbd>: Checkout file
| `` c `` | Checkout | Checkout file. This replaces the file in your working tree with the version from the selected commit. | <kbd>d</kbd>: Discard this commit's changes to this file
| `` d `` | Remove | 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. | <kbd>o</kbd>: Open file
| `` o `` | Open file | Open file in default application. | <kbd>e</kbd>: Edit file
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Toggle file included in patch
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | 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. | <kbd>&lt;enter&gt;</kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: Toggle file tree view
| `` <enter> `` | 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. | <kbd>/</kbd>: Search the current view by text
| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | Search the current view by text | |
## Commit summary ## Commit summary
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Confirm
| `` <enter> `` | Confirm | | <kbd>&lt;esc&gt;</kbd>: Close
| `` <esc> `` | Close | | </pre>
## Commits ## Commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Copy commit hash to clipboard | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>b</kbd>: View bisect options
| `` b `` | View bisect options | | <kbd>s</kbd>: Squash down
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: Fixup commit
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: Reword commit
| `` r `` | Reword | Reword the selected commit's message. | <kbd>R</kbd>: Reword commit with editor
| `` R `` | Reword with editor | | <kbd>d</kbd>: Delete commit
| `` d `` | Drop | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: Edit commit
| `` e `` | Edit (start interactive rebase) | Edit the selected commit. Use this to start an interactive rebase from the selected commit. When already mid-rebase, this will mark the selected commit for editing, which means that upon continuing the rebase, the rebase will pause at the selected commit to allow you to make changes. | <kbd>p</kbd>: Pick commit (when mid-rebase)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: Create fixup commit for this commit
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: Squash all 'fixup!' commits above selected commit (autosquash)
| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. | <kbd>&lt;c-j&gt;</kbd>: Move commit down one
| `` 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. | <kbd>&lt;c-k&gt;</kbd>: Move commit up one
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). | <kbd>v</kbd>: Paste commits (cherry-pick)
| `` <c-j> `` | Move commit down one | | <kbd>A</kbd>: Amend commit with staged changes
| `` <c-k> `` | Move commit up one | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | Paste (cherry-pick) | | <kbd>t</kbd>: Revert commit
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: Tag commit
| `` A `` | Amend | Amend commit with staged changes. If the selected commit is the HEAD commit, this will perform `git commit --amend`. Otherwise the commit will be amended via a rebase. | <kbd>&lt;c-l&gt;</kbd>: Open log menu
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: Copy commit attribute
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: Open commit in browser
| `` <c-l> `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: Create new branch off of commit
| `` <space> `` | Checkout | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: View reset options
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: Copy commit (cherry-pick)
| `` o `` | Open commit in browser | | <kbd>C</kbd>: Copy commit range (cherry-pick)
| `` n `` | Create new branch off of commit | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Search the current view by text
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Search the current view by text | |
## Confirmation panel ## Confirmation panel
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Confirm
| `` <enter> `` | Confirm | | <kbd>&lt;esc&gt;</kbd>: Close/Cancel
| `` <esc> `` | Close/Cancel | | </pre>
## Files ## Files
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy the file name to the clipboard
| `` <c-o> `` | Copy path to clipboard | | <kbd>d</kbd>: View 'discard changes' options
| `` <space> `` | Stage | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: Toggle staged
| `` <c-b> `` | Filter files by status | | <kbd>&lt;c-b&gt;</kbd>: Filter files by status
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: Commit changes
| `` c `` | Commit | Commit staged changes. | <kbd>w</kbd>: Commit changes without pre-commit hook
| `` w `` | Commit changes without pre-commit hook | | <kbd>A</kbd>: Amend last commit
| `` A `` | Amend last commit | | <kbd>C</kbd>: Commit changes using git editor
| `` C `` | Commit changes using git editor | | <kbd>e</kbd>: Edit file
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: Open file
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: Ignore or exclude file
| `` o `` | Open file | Open file in default application. | <kbd>r</kbd>: Refresh files
| `` i `` | Ignore or exclude file | | <kbd>s</kbd>: Stash all changes
| `` r `` | Refresh files | | <kbd>S</kbd>: View stash options
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: Stage/unstage all
| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: Stage individual hunks/lines for file, or collapse/expand for directory
| `` a `` | Stage all | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: View upstream reset options
| `` <enter> `` | Stage lines / Collapse directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: View reset options
| `` d `` | Discard | View options for discarding changes to the selected file. | <kbd>`</kbd>: Toggle file tree view
| `` g `` | View upstream reset options | | <kbd>M</kbd>: Open external merge tool (git mergetool)
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: Fetch
| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: Search the current view by text
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` / `` | Search the current view by text | |
## Local branches ## Local branches
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy branch name to clipboard
| `` <c-o> `` | Copy branch name to clipboard | | <kbd>i</kbd>: Show git-flow options
| `` i `` | Show git-flow options | | <kbd>&lt;space&gt;</kbd>: Checkout
| `` <space> `` | Checkout | Checkout selected item. | <kbd>n</kbd>: New branch
| `` n `` | New branch | | <kbd>o</kbd>: Create pull request
| `` o `` | Create pull request | | <kbd>O</kbd>: Create pull request options
| `` O `` | View create pull request options | | <kbd>&lt;c-y&gt;</kbd>: Copy pull request URL to clipboard
| `` <c-y> `` | Copy pull request URL to clipboard | | <kbd>c</kbd>: Checkout by name
| `` c `` | Checkout by name | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: Force checkout
| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: Delete branch
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: Rebase checked-out branch onto this branch
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: Merge into currently checked out branch
| `` M `` | Merge | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: Fast-forward this branch from its upstream
| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: Create tag
| `` T `` | New tag | | <kbd>g</kbd>: View reset options
| `` s `` | Sort order | | <kbd>R</kbd>: Rename branch
| `` g `` | Reset | | <kbd>u</kbd>: Set/Unset upstream
| `` R `` | Rename branch | | <kbd>&lt;enter&gt;</kbd>: View commits
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | View commits | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Main panel (merging) ## Main panel (merging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: Edit file
| `` <space> `` | Pick hunk | | <kbd>o</kbd>: Open file
| `` b `` | Pick all hunks | | <kbd>&lt;left&gt;</kbd>: Select previous conflict
| `` <up> `` | Previous hunk | | <kbd>&lt;right&gt;</kbd>: Select next conflict
| `` <down> `` | Next hunk | | <kbd>&lt;up&gt;</kbd>: Select previous hunk
| `` <left> `` | Previous conflict | | <kbd>&lt;down&gt;</kbd>: Select next hunk
| `` <right> `` | Next conflict | | <kbd>z</kbd>: Undo
| `` z `` | Undo | Undo last merge conflict resolution. | <kbd>M</kbd>: Open external merge tool (git mergetool)
| `` e `` | Edit file | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Pick hunk
| `` o `` | Open file | Open file in default application. | <kbd>b</kbd>: Pick all hunks
| `` M `` | Open external merge tool | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: Return to files panel
| `` <esc> `` | Return to files panel | | </pre>
## Main panel (normal) ## Main panel (normal)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: Scroll down (fn+up)
| `` mouse wheel down (fn+up) `` | Scroll down | | <kbd>mouse wheel up</kbd>: Scroll up (fn+down)
| `` mouse wheel up (fn+down) `` | Scroll up | | </pre>
## Main panel (patch building) ## Main panel (patch building)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Select previous hunk
| `` <left> `` | Go to previous hunk | | <kbd>&lt;right&gt;</kbd>: Select next hunk
| `` <right> `` | Go to next hunk | | <kbd>v</kbd>: Toggle drag select
| `` v `` | Toggle range select | | <kbd>V</kbd>: Toggle drag select
| `` a `` | Select hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle select hunk
| `` <c-o> `` | Copy selected text to clipboard | | <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
| `` o `` | Open file | Open file in default application. | <kbd>o</kbd>: Open file
| `` e `` | Edit file | Open file in external editor. | <kbd>e</kbd>: Edit file
| `` <space> `` | Toggle lines in patch | | <kbd>&lt;space&gt;</kbd>: Add/Remove line(s) to patch
| `` <esc> `` | Exit custom patch builder | | <kbd>&lt;esc&gt;</kbd>: Exit custom patch builder
| `` / `` | Search the current view by text | | <kbd>/</kbd>: Search the current view by text
</pre>
## Main panel (staging) ## Main panel (staging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Select previous hunk
| `` <left> `` | Go to previous hunk | | <kbd>&lt;right&gt;</kbd>: Select next hunk
| `` <right> `` | Go to next hunk | | <kbd>v</kbd>: Toggle drag select
| `` v `` | Toggle range select | | <kbd>V</kbd>: Toggle drag select
| `` a `` | Select hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle select hunk
| `` <c-o> `` | Copy selected text to clipboard | | <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
| `` <space> `` | Stage | Toggle selection staged / unstaged. | <kbd>o</kbd>: Open file
| `` d `` | Discard | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: Edit file
| `` o `` | Open file | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: Return to files panel
| `` e `` | Edit file | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: Switch to other panel (staged/unstaged changes)
| `` <esc> `` | Return to files panel | | <kbd>&lt;space&gt;</kbd>: Toggle line staged / unstaged
| `` <tab> `` | Switch view | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: Discard change (git reset)
| `` E `` | Edit hunk | Edit selected hunk in external editor. | <kbd>E</kbd>: Edit hunk
| `` c `` | Commit | Commit staged changes. | <kbd>c</kbd>: Commit changes
| `` w `` | Commit changes without pre-commit hook | | <kbd>w</kbd>: Commit changes without pre-commit hook
| `` C `` | Commit changes using git editor | | <kbd>C</kbd>: Commit changes using git editor
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: Search the current view by text
| `` / `` | Search the current view by text | | </pre>
## Menu ## Menu
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Execute
| `` <enter> `` | Execute | | <kbd>&lt;esc&gt;</kbd>: Close
| `` <esc> `` | Close | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Reflog ## Reflog
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Copy commit hash to clipboard | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Checkout | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Open commit in browser | | <kbd>n</kbd>: Create new branch off of commit
| `` n `` | Create new branch off of commit | | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Copy commit (cherry-pick)
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Copy commit range (cherry-pick)
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: View commits
| `` <enter> `` | View commits | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy branch name to clipboard
| `` <c-o> `` | Copy branch name to clipboard | | <kbd>&lt;space&gt;</kbd>: Checkout
| `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: New branch
| `` n `` | New branch | | <kbd>M</kbd>: Merge into currently checked out branch
| `` M `` | Merge | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: Rebase checked-out branch onto this branch
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: Delete branch
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: Set as upstream of checked-out branch
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. | <kbd>g</kbd>: View reset options
| `` s `` | Sort order | | <kbd>&lt;enter&gt;</kbd>: View commits
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | View commits | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Remotes ## Remotes
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: Fetch remote
| `` <enter> `` | View branches | | <kbd>n</kbd>: Add new remote
| `` n `` | New remote | | <kbd>d</kbd>: Remove remote
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: Edit remote
| `` e `` | Edit | Edit the selected remote's name or URL. | <kbd>/</kbd>: Filter the current view by text
| `` f `` | Fetch | Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches. | </pre>
| `` / `` | Filter the current view by text | |
## Stash ## Stash
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Apply
| `` <space> `` | Apply | Apply the stash entry to your working directory. | <kbd>g</kbd>: Pop
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: Drop
| `` d `` | Drop | Remove the stash entry from the stash list. | <kbd>n</kbd>: New branch
| `` n `` | New branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Rename stash
| `` r `` | Rename stash | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View files | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: Open config file
| `` o `` | Open config file | Open file in default application. | <kbd>e</kbd>: Edit config file
| `` e `` | Edit config file | Open file in external editor. | <kbd>u</kbd>: Check for update
| `` u `` | Check for update | | <kbd>&lt;enter&gt;</kbd>: Switch to a recent repo
| `` <enter> `` | Switch to a recent repo | | <kbd>a</kbd>: Show all branch logs
| `` a `` | Show all branch logs | | </pre>
## Sub-commits ## Sub-commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Copy commit hash to clipboard | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Checkout | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Open commit in browser | | <kbd>n</kbd>: Create new branch off of commit
| `` n `` | Create new branch off of commit | | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Copy commit (cherry-pick)
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Copy commit range (cherry-pick)
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View files | | <kbd>/</kbd>: Search the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Search the current view by text | |
## Submodules ## Submodules
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy submodule name to clipboard
| `` <c-o> `` | Copy submodule name to clipboard | | <kbd>&lt;enter&gt;</kbd>: Enter submodule
| `` <enter> `` | Enter | Enter submodule. After entering the submodule, you can press `<esc>` to escape back to the parent repo. | <kbd>d</kbd>: Remove submodule
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: Update submodule
| `` u `` | Update | Update selected submodule. | <kbd>n</kbd>: Add new submodule
| `` n `` | New submodule | | <kbd>e</kbd>: Update submodule URL
| `` e `` | Update submodule URL | | <kbd>i</kbd>: Initialize submodule
| `` i `` | Initialize | Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule. | <kbd>b</kbd>: View bulk submodule options
| `` b `` | View bulk submodule options | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Tags ## Tags
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Checkout
| `` <space> `` | Checkout | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: Delete tag
| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: Push tag
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: Create tag
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: View commits
| `` <enter> `` | View commits | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit キーバインド # Lazygit キーバインド
@@ -6,359 +6,328 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## グローバルキーバインド ## グローバルキーバインド
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: 最近使用したリポジトリに切り替え
| `` <c-r> `` | 最近使用したリポジトリに切り替え | | <kbd>&lt;pgup&gt;</kbd>: メインパネルを上にスクロール (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | メインパネルを上にスクロール | | <kbd>&lt;pgdown&gt;</kbd>: メインパネルを下にスクロール (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | メインパネルを下にスクロール | | <kbd>@</kbd>: コマンドログメニューを開く
| `` @ `` | コマンドログメニューを開く | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
| `` 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. | <kbd>:</kbd>: カスタムコマンドを実行
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: View custom patch options
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: View merge/rebase options
| `` : `` | カスタムコマンドを実行 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: リフレッシュ
| `` <c-p> `` | View custom patch options | | <kbd>+</kbd>: 次のスクリーンモード (normal/half/fullscreen)
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: 前のスクリーンモード
| `` R `` | リフレッシュ | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: メニューを開く
| `` + `` | 次のスクリーンモード (normal/half/fullscreen) | | <kbd>&lt;c-s&gt;</kbd>: View filter-by-path options
| `` _ `` | 前のスクリーンモード | | <kbd>W</kbd>: 差分メニューを開く
| `` ? `` | メニューを開く | | <kbd>&lt;c-e&gt;</kbd>: 差分メニューを開く
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: 空白文字の差分の表示有無を切り替え
| `` W `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: アンドゥ (via reflog) (experimental)
| `` <c-e> `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: リドゥ (via reflog) (experimental)
| `` q `` | 終了 | | <kbd>P</kbd>: Push
| `` <esc> `` | キャンセル | | <kbd>p</kbd>: Pull
| `` <c-w> `` | 空白文字の差分の表示有無を切り替え | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | アンドゥ (via reflog) (experimental) | 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. |
| `` <c-z> `` | リドゥ (via reflog) (experimental) | 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. |
## 一覧パネルの操作 ## 一覧パネルの操作
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: 前のページ
| `` , `` | 前のページ | | <kbd>.</kbd>: 次のページ
| `` . `` | 次のページ | | <kbd>&lt;</kbd>: 最上部までスクロール
| `` < `` | 最上部までスクロール | | <kbd>&gt;</kbd>: 最下部までスクロール
| `` > `` | 最下部までスクロール | | <kbd>/</kbd>: 検索を開始
| `` v `` | 範囲選択を切り替え | | <kbd>H</kbd>: 左スクロール
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: 右スクロール
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: 次のタブ
| `` / `` | 検索を開始 | | <kbd>[</kbd>: 前のタブ
| `` H `` | 左スクロール | | </pre>
| `` L `` | 右スクロール | |
| `` ] `` | 次のタブ | |
| `` [ `` | 前のタブ | |
## Stash ## Stash
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 適用
| `` <space> `` | 適用 | Apply the stash entry to your working directory. | <kbd>g</kbd>: Pop
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: Drop
| `` d `` | Drop | Remove the stash entry from the stash list. | <kbd>n</kbd>: 新しいブランチを作成
| `` n `` | 新しいブランチを作成 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Stashを変更
| `` r `` | Stashを変更 | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View files | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Sub-commits ## Sub-commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
| `` <c-o> `` | コミットのhashをクリップボードにコピー | | <kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: コミットの情報をコピー
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: ブラウザでコミットを開く
| `` o `` | ブラウザでコミットを開く | | <kbd>n</kbd>: コミットにブランチを作成
| `` n `` | コミットにブランチを作成 | | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: コミットをコピー (cherry-pick)
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View files | | <kbd>/</kbd>: 検索を開始
| `` w `` | View worktree options | | </pre>
| `` / `` | 検索を開始 | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
## コミット ## コミット
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
| `` <c-o> `` | コミットのhashをクリップボードにコピー | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>b</kbd>: View bisect options
| `` b `` | View bisect options | | <kbd>s</kbd>: Squash down
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: Fixup commit
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: コミットメッセージを変更
| `` r `` | コミットメッセージを変更 | Reword the selected commit's message. | <kbd>R</kbd>: エディタでコミットメッセージを編集
| `` R `` | エディタでコミットメッセージを編集 | | <kbd>d</kbd>: コミットを削除
| `` d `` | コミットを削除 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: コミットを編集
| `` e `` | Edit (start interactive rebase) | コミットを編集 | <kbd>p</kbd>: Pick commit (when mid-rebase)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: このコミットに対するfixupコミットを作成
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: Squash all 'fixup!' commits above selected commit (autosquash)
| `` p `` | Pick | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. | <kbd>&lt;c-j&gt;</kbd>: コミットを1つ下に移動
| `` F `` | Fixupコミットを作成 | このコミットに対するfixupコミットを作成 | <kbd>&lt;c-k&gt;</kbd>: コミットを1つ上に移動
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). | <kbd>v</kbd>: コミットを貼り付け (cherry-pick)
| `` <c-j> `` | コミットを1つ下に移動 | | <kbd>A</kbd>: ステージされた変更でamendコミット
| `` <c-k> `` | コミットを1つ上に移動 | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | コミットを貼り付け (cherry-pick) | | <kbd>t</kbd>: コミットをrevert
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: タグを作成
| `` A `` | Amend | ステージされた変更でamendコミット | <kbd>&lt;c-l&gt;</kbd>: ログメニューを開く
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: コミットの情報をコピー
| `` T `` | タグを作成 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: ブラウザでコミットを開く
| `` <c-l> `` | ログメニューを開く | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: コミットにブランチを作成
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: View reset options
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: コミットをコピー (cherry-pick)
| `` o `` | ブラウザでコミットを開く | | <kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
| `` n `` | コミットにブランチを作成 | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: 検索を開始
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | 検索を開始 | |
## コミットファイル ## コミットファイル
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: コミットされたファイル名をクリップボードにコピー
| `` <c-o> `` | ファイル名をクリップボードにコピー | | <kbd>c</kbd>: Checkout file
| `` c `` | チェックアウト | Checkout file. This replaces the file in your working tree with the version from the selected commit. | <kbd>d</kbd>: Discard this commit's changes to this file
| `` d `` | Remove | 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. | <kbd>o</kbd>: ファイルを開く
| `` o `` | ファイルを開く | Open file in default application. | <kbd>e</kbd>: ファイルを編集
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Toggle file included in patch
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | 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. | <kbd>&lt;enter&gt;</kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: ファイルツリーの表示を切り替え
| `` <enter> `` | 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. | <kbd>/</kbd>: 検索を開始
| `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | 検索を開始 | |
## コミットメッセージ ## コミットメッセージ
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 確認
| `` <enter> `` | 確認 | | <kbd>&lt;esc&gt;</kbd>: 閉じる
| `` <esc> `` | 閉じる | | </pre>
## サブモジュール ## サブモジュール
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: サブモジュール名をクリップボードにコピー
| `` <c-o> `` | サブモジュール名をクリップボードにコピー | | <kbd>&lt;enter&gt;</kbd>: サブモジュールを開く
| `` <enter> `` | Enter | サブモジュールを開く | <kbd>d</kbd>: サブモジュールを削除
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: サブモジュールを更新
| `` u `` | Update | サブモジュールを更新 | <kbd>n</kbd>: サブモジュールを新規追加
| `` n `` | サブモジュールを新規追加 | | <kbd>e</kbd>: サブモジュールのURLを更新
| `` e `` | サブモジュールのURLを更新 | | <kbd>i</kbd>: サブモジュールを初期化
| `` i `` | Initialize | サブモジュールを初期化 | <kbd>b</kbd>: View bulk submodule options
| `` b `` | View bulk submodule options | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## ステータス ## ステータス
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: 設定ファイルを開く
| `` o `` | 設定ファイルを開く | Open file in default application. | <kbd>e</kbd>: 設定ファイルを編集
| `` e `` | 設定ファイルを編集 | Open file in external editor. | <kbd>u</kbd>: 更新を確認
| `` u `` | 更新を確認 | | <kbd>&lt;enter&gt;</kbd>: 最近使用したリポジトリに切り替え
| `` <enter> `` | 最近使用したリポジトリに切り替え | | <kbd>a</kbd>: すべてのブランチログを表示
| `` a `` | すべてのブランチログを表示 | | </pre>
## タグ ## タグ
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: チェックアウト
| `` <space> `` | チェックアウト | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: タグを削除
| `` n `` | タグを作成 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: タグをpush
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: タグを作成
| `` P `` | タグをpush | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: コミットを閲覧
| `` <enter> `` | コミットを閲覧 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## ファイル ## ファイル
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: ファイル名をクリップボードにコピー
| `` <c-o> `` | ファイル名をクリップボードにコピー | | <kbd>d</kbd>: View 'discard changes' options
| `` <space> `` | ステージ/アンステージ | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: ステージ/アンステージ
| `` <c-b> `` | ファイルをフィルタ (ステージ/アンステージ) | | <kbd>&lt;c-b&gt;</kbd>: ファイルをフィルタ (ステージ/アンステージ)
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: 変更をコミット
| `` c `` | 変更をコミット | Commit staged changes. | <kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
| `` w `` | pre-commitフックを実行せずに変更をコミット | | <kbd>A</kbd>: 最新のコミットにamend
| `` A `` | 最新のコミットにamend | | <kbd>C</kbd>: gitエディタを使用して変更をコミット
| `` C `` | gitエディタを使用して変更をコミット | | <kbd>e</kbd>: ファイルを編集
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: ファイルを開く
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: ファイルをignore
| `` o `` | ファイルを開く | Open file in default application. | <kbd>r</kbd>: ファイルをリフレッシュ
| `` i `` | ファイルをignore | | <kbd>s</kbd>: 変更をstash
| `` r `` | ファイルをリフレッシュ | | <kbd>S</kbd>: View stash options
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: すべての変更をステージ/アンステージ
| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: Stage individual hunks/lines for file, or collapse/expand for directory
| `` a `` | すべての変更をステージ/アンステージ | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: View upstream reset options
| `` <enter> `` | Stage lines / Collapse directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: View reset options
| `` d `` | Discard | View options for discarding changes to the selected file. | <kbd>`</kbd>: ファイルツリーの表示を切り替え
| `` g `` | View upstream reset options | | <kbd>M</kbd>: Git mergetoolを開く
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: Fetch
| `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: 検索を開始
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | Git mergetoolを開く | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` / `` | 検索を開始 | |
## ブランチ ## ブランチ
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: ブランチ名をクリップボードにコピー
| `` <c-o> `` | ブランチ名をクリップボードにコピー | | <kbd>i</kbd>: Show git-flow options
| `` i `` | Show git-flow options | | <kbd>&lt;space&gt;</kbd>: チェックアウト
| `` <space> `` | チェックアウト | Checkout selected item. | <kbd>n</kbd>: 新しいブランチを作成
| `` n `` | 新しいブランチを作成 | | <kbd>o</kbd>: Pull Requestを作成
| `` o `` | Pull Requestを作成 | | <kbd>O</kbd>: Create pull request options
| `` O `` | View create pull request options | | <kbd>&lt;c-y&gt;</kbd>: Pull RequestのURLをクリップボードにコピー
| `` <c-y> `` | Pull RequestのURLをクリップボードにコピー | | <kbd>c</kbd>: Checkout by name
| `` c `` | Checkout by name | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: Force checkout
| `` F `` | Force checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: ブランチを削除
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: Rebase checked-out branch onto this branch
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: 現在のブランチにマージ
| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: Fast-forward this branch from its upstream
| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: タグを作成
| `` T `` | タグを作成 | | <kbd>g</kbd>: View reset options
| `` s `` | 並び替え | | <kbd>R</kbd>: ブランチ名を変更
| `` g `` | Reset | | <kbd>u</kbd>: Set/Unset upstream
| `` R `` | ブランチ名を変更 | | <kbd>&lt;enter&gt;</kbd>: コミットを閲覧
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | コミットを閲覧 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## メインパネル (Merging) ## メインパネル (Merging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: ファイルを編集
| `` <space> `` | Pick hunk | | <kbd>o</kbd>: ファイルを開く
| `` b `` | Pick all hunks | | <kbd>&lt;left&gt;</kbd>: 前のコンフリクトを選択
| `` <up> `` | 前のhunkを選択 | | <kbd>&lt;right&gt;</kbd>: 次のコンフリクトを選択
| `` <down> `` | 次のhunkを選択 | | <kbd>&lt;up&gt;</kbd>: 前のhunkを選択
| `` <left> `` | 前のコンフリクトを選択 | | <kbd>&lt;down&gt;</kbd>: 次のhunkを選択
| `` <right> `` | 次のコンフリクトを選択 | | <kbd>z</kbd>: アンドゥ
| `` z `` | アンドゥ | Undo last merge conflict resolution. | <kbd>M</kbd>: Git mergetoolを開く
| `` e `` | ファイルを編集 | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Pick hunk
| `` o `` | ファイルを開く | Open file in default application. | <kbd>b</kbd>: Pick all hunks
| `` M `` | Git mergetoolを開く | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: ファイル一覧に戻る
| `` <esc> `` | ファイル一覧に戻る | | </pre>
## メインパネル (Normal) ## メインパネル (Normal)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
| `` mouse wheel down (fn+up) `` | 下にスクロール | | <kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
| `` mouse wheel up (fn+down) `` | 上にスクロール | | </pre>
## メインパネル (Patch Building) ## メインパネル (Patch Building)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 前のhunkを選択
| `` <left> `` | 前のhunkを選択 | | <kbd>&lt;right&gt;</kbd>: 次のhunkを選択
| `` <right> `` | 次のhunkを選択 | | <kbd>v</kbd>: 範囲選択を切り替え
| `` v `` | 範囲選択を切り替え | | <kbd>V</kbd>: 範囲選択を切り替え
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. | <kbd>a</kbd>: Hunk選択を切り替え
| `` <c-o> `` | 選択されたテキストをクリップボードにコピー | | <kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
| `` o `` | ファイルを開く | Open file in default application. | <kbd>o</kbd>: ファイルを開く
| `` e `` | ファイルを編集 | Open file in external editor. | <kbd>e</kbd>: ファイルを編集
| `` <space> `` | 行をパッチに追加/削除 | | <kbd>&lt;space&gt;</kbd>: 行をパッチに追加/削除
| `` <esc> `` | Exit custom patch builder | | <kbd>&lt;esc&gt;</kbd>: Exit custom patch builder
| `` / `` | 検索を開始 | | <kbd>/</kbd>: 検索を開始
</pre>
## メインパネル (Staging) ## メインパネル (Staging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 前のhunkを選択
| `` <left> `` | 前のhunkを選択 | | <kbd>&lt;right&gt;</kbd>: 次のhunkを選択
| `` <right> `` | 次のhunkを選択 | | <kbd>v</kbd>: 範囲選択を切り替え
| `` v `` | 範囲選択を切り替え | | <kbd>V</kbd>: 範囲選択を切り替え
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. | <kbd>a</kbd>: Hunk選択を切り替え
| `` <c-o> `` | 選択されたテキストをクリップボードにコピー | | <kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
| `` <space> `` | ステージ/アンステージ | 選択行をステージ/アンステージ | <kbd>o</kbd>: ファイルを開く
| `` d `` | 変更を削除 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: ファイルを編集
| `` o `` | ファイルを開く | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: ファイル一覧に戻る
| `` e `` | ファイルを編集 | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: パネルを切り替え
| `` <esc> `` | ファイル一覧に戻る | | <kbd>&lt;space&gt;</kbd>: 選択行をステージ/アンステージ
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: 変更を削除 (git reset)
| `` E `` | Edit hunk | Edit selected hunk in external editor. | <kbd>E</kbd>: Edit hunk
| `` c `` | 変更をコミット | Commit staged changes. | <kbd>c</kbd>: 変更をコミット
| `` w `` | pre-commitフックを実行せずに変更をコミット | | <kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
| `` C `` | gitエディタを使用して変更をコミット | | <kbd>C</kbd>: gitエディタを使用して変更をコミット
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: 検索を開始
| `` / `` | 検索を開始 | | </pre>
## メニュー ## メニュー
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 実行
| `` <enter> `` | 実行 | | <kbd>&lt;esc&gt;</kbd>: 閉じる
| `` <esc> `` | 閉じる | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## リモート ## リモート
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: リモートをfetch
| `` <enter> `` | View branches | | <kbd>n</kbd>: リモートを新規追加
| `` n `` | リモートを新規追加 | | <kbd>d</kbd>: リモートを削除
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: リモートを編集
| `` e `` | Edit | リモートを編集 | <kbd>/</kbd>: Filter the current view by text
| `` f `` | Fetch | リモートをfetch | </pre>
| `` / `` | Filter the current view by text | |
## リモートブランチ ## リモートブランチ
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: ブランチ名をクリップボードにコピー
| `` <c-o> `` | ブランチ名をクリップボードにコピー | | <kbd>&lt;space&gt;</kbd>: チェックアウト
| `` <space> `` | チェックアウト | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: 新しいブランチを作成
| `` n `` | 新しいブランチを作成 | | <kbd>M</kbd>: 現在のブランチにマージ
| `` M `` | 現在のブランチにマージ | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: Rebase checked-out branch onto this branch
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: ブランチを削除
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: Set as upstream of checked-out branch
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. | <kbd>g</kbd>: View reset options
| `` s `` | 並び替え | | <kbd>&lt;enter&gt;</kbd>: コミットを閲覧
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | コミットを閲覧 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 参照ログ ## 参照ログ
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
| `` <c-o> `` | コミットのhashをクリップボードにコピー | | <kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: コミットの情報をコピー
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: ブラウザでコミットを開く
| `` o `` | ブラウザでコミットを開く | | <kbd>n</kbd>: コミットにブランチを作成
| `` n `` | コミットにブランチを作成 | | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: コミットをコピー (cherry-pick)
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: コミットを閲覧
| `` <enter> `` | コミットを閲覧 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## 確認パネル ## 確認パネル
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 確認
| `` <enter> `` | 確認 | | <kbd>&lt;esc&gt;</kbd>: 閉じる/キャンセル
| `` <esc> `` | 閉じる/キャンセル | | </pre>
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 키 바인딩 # Lazygit 키 바인딩
@@ -6,359 +6,328 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 글로벌 키 바인딩 ## 글로벌 키 바인딩
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: 최근에 사용한 저장소로 전환
| `` <c-r> `` | 최근에 사용한 저장소로 전환 | | <kbd>&lt;pgup&gt;</kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | 메인 패널을 위로 스크롤 | | <kbd>&lt;pgdown&gt;</kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | 메인 패널을 아래로로 스크롤 | | <kbd>@</kbd>: 명령어 로그 메뉴 열기
| `` @ `` | 명령어 로그 메뉴 열기 | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
| `` P `` | 푸시 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기
| `` p `` | 업데이트 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>:</kbd>: Execute custom command
| `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: 커스텀 Patch 옵션 보기
| `` { `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기 | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: View merge/rebase options
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: 새로고침
| `` <c-p> `` | 커스텀 Patch 옵션 보기 | | <kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: 이전 스크린 모드
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: 매뉴 열기
| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | | <kbd>&lt;c-s&gt;</kbd>: View filter-by-path options
| `` _ `` | 이전 스크린 모드 | | <kbd>W</kbd>: Diff 메뉴 열기
| `` ? `` | 매뉴 열기 | | <kbd>&lt;c-e&gt;</kbd>: Diff 메뉴 열기
| `` <c-s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
| `` W `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: 되돌리기 (reflog) (실험적)
| `` <c-e> `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: 다시 실행 (reflog) (실험적)
| `` q `` | 종료 | | <kbd>P</kbd>: 푸시
| `` <esc> `` | 취소 | | <kbd>p</kbd>: 업데이트
| `` <c-w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## List panel navigation ## List panel navigation
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: 이전 페이지
| `` , `` | 이전 페이지 | | <kbd>.</kbd>: 다음 페이지
| `` . `` | 다음 페이지 | | <kbd>&lt;</kbd>: 맨 위로 스크롤
| `` < `` | 맨 위로 스크롤 | | <kbd>&gt;</kbd>: 맨 아래로 스크롤
| `` > `` | 맨 아래로 스크롤 | | <kbd>/</kbd>: 검색 시작
| `` v `` | 드래그 선택 전환 | | <kbd>H</kbd>: 우 스크롤
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: 좌 스크롤
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: 이전 탭
| `` / `` | 검색 시작 | | <kbd>[</kbd>: 다음 탭
| `` H `` | 우 스크롤 | | </pre>
| `` L `` | 좌 스크롤 | |
| `` ] `` | 이전 탭 | |
| `` [ `` | 다음 탭 | |
## Reflog ## Reflog
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
| `` <c-o> `` | 커밋 해시를 클립보드에 복사 | | <kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
| `` <space> `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: 커밋 attribute 복사
| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: 브라우저에서 커밋 열기
| `` o `` | 브라우저에서 커밋 열기 | | <kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | <kbd>g</kbd>: View reset options
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: 커밋을 복사 (cherry-pick)
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
| `` <c-r> `` | Reset cherry-picked (copied) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: 커밋 보기
| `` <enter> `` | 커밋 보기 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Stash ## Stash
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 적용
| `` <space> `` | 적용 | Apply the stash entry to your working directory. | <kbd>g</kbd>: Pop
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: Drop
| `` d `` | Drop | Remove the stash entry from the stash list. | <kbd>n</kbd>: 새 브랜치 생성
| `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Rename stash
| `` r `` | Rename stash | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View selected item's files | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Sub-commits ## Sub-commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
| `` <c-o> `` | 커밋 해시를 클립보드에 복사 | | <kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
| `` <space> `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: 커밋 attribute 복사
| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: 브라우저에서 커밋 열기
| `` o `` | 브라우저에서 커밋 열기 | | <kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | <kbd>g</kbd>: View reset options
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: 커밋을 복사 (cherry-pick)
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
| `` <c-r> `` | Reset cherry-picked (copied) commits selection | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` <enter> `` | View selected item's files | | <kbd>/</kbd>: 검색 시작
| `` w `` | View worktree options | | </pre>
| `` / `` | 검색 시작 | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
## 메뉴 ## 메뉴
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 실행
| `` <enter> `` | 실행 | | <kbd>&lt;esc&gt;</kbd>: 닫기
| `` <esc> `` | 닫기 | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## 메인 패널 (Merging) ## 메인 패널 (Merging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: 파일 편집
| `` <space> `` | Pick hunk | | <kbd>o</kbd>: 파일 닫기
| `` b `` | Pick all hunks | | <kbd>&lt;left&gt;</kbd>: 이전 충돌을 선택
| `` <up> `` | 이전 hunk를 선택 | | <kbd>&lt;right&gt;</kbd>: 다음 충돌을 선택
| `` <down> `` | 다음 hunk를 선택 | | <kbd>&lt;up&gt;</kbd>: 이전 hunk를 선택
| `` <left> `` | 이전 충돌을 선택 | | <kbd>&lt;down&gt;</kbd>: 다음 hunk를 선택
| `` <right> `` | 다음 충돌을 선택 | | <kbd>z</kbd>: 되돌리기
| `` z `` | 되돌리기 | Undo last merge conflict resolution. | <kbd>M</kbd>: Git mergetool를 열기
| `` e `` | 파일 편집 | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Pick hunk
| `` o `` | 파일 닫기 | Open file in default application. | <kbd>b</kbd>: Pick all hunks
| `` M `` | Git mergetool를 열기 | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: 파일 목록으로 돌아가기
| `` <esc> `` | 파일 목록으로 돌아가기 | | </pre>
## 메인 패널 (Normal) ## 메인 패널 (Normal)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
| `` mouse wheel down (fn+up) `` | 아래로 스크롤 | | <kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
| `` mouse wheel up (fn+down) `` | 위로 스크롤 | | </pre>
## 메인 패널 (Patch Building) ## 메인 패널 (Patch Building)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
| `` <left> `` | 이전 hunk를 선택 | | <kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
| `` <right> `` | 다음 hunk를 선택 | | <kbd>v</kbd>: 드래그 선택 전환
| `` v `` | 드래그 선택 전환 | | <kbd>V</kbd>: 드래그 선택 전환
| `` a `` | Toggle select hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle select hunk
| `` <c-o> `` | 선택한 텍스트를 클립보드에 복사 | | <kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
| `` o `` | 파일 닫기 | Open file in default application. | <kbd>o</kbd>: 파일 닫기
| `` e `` | 파일 편집 | Open file in external editor. | <kbd>e</kbd>: 파일 편집
| `` <space> `` | Line(s)을 패치에 추가/삭제 | | <kbd>&lt;space&gt;</kbd>: Line(s)을 패치에 추가/삭제
| `` <esc> `` | Exit custom patch builder | | <kbd>&lt;esc&gt;</kbd>: Exit custom patch builder
| `` / `` | 검색 시작 | | <kbd>/</kbd>: 검색 시작
</pre>
## 메인 패널 (Staging) ## 메인 패널 (Staging)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
| `` <left> `` | 이전 hunk를 선택 | | <kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
| `` <right> `` | 다음 hunk를 선택 | | <kbd>v</kbd>: 드래그 선택 전환
| `` v `` | 드래그 선택 전환 | | <kbd>V</kbd>: 드래그 선택 전환
| `` a `` | Toggle select hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle select hunk
| `` <c-o> `` | 선택한 텍스트를 클립보드에 복사 | | <kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
| `` <space> `` | Staged 전환 | 선택한 행을 staged / unstaged | <kbd>o</kbd>: 파일 닫기
| `` d `` | 변경을 삭제 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: 파일 편집
| `` o `` | 파일 닫기 | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: 파일 목록으로 돌아가기
| `` e `` | 파일 편집 | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: 패널 전환
| `` <esc> `` | 파일 목록으로 돌아가기 | | <kbd>&lt;space&gt;</kbd>: 선택한 행을 staged / unstaged
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: 변경을 삭제 (git reset)
| `` E `` | Edit hunk | Edit selected hunk in external editor. | <kbd>E</kbd>: Edit hunk
| `` c `` | 커밋 변경내용 | Commit staged changes. | <kbd>c</kbd>: 커밋 변경내용
| `` w `` | Commit changes without pre-commit hook | | <kbd>w</kbd>: Commit changes without pre-commit hook
| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | | <kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: 검색 시작
| `` / `` | 검색 시작 | | </pre>
## 브랜치 ## 브랜치
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 브랜치명을 클립보드에 복사
| `` <c-o> `` | 브랜치명을 클립보드에 복사 | | <kbd>i</kbd>: Git-flow 옵션 보기
| `` i `` | Git-flow 옵션 보기 | | <kbd>&lt;space&gt;</kbd>: 체크아웃
| `` <space> `` | 체크아웃 | Checkout selected item. | <kbd>n</kbd>: 새 브랜치 생성
| `` n `` | 새 브랜치 생성 | | <kbd>o</kbd>: 풀 리퀘스트 생성
| `` o `` | 풀 리퀘스트 생성 | | <kbd>O</kbd>: 풀 리퀘스트 생성 옵션
| `` O `` | 풀 리퀘스트 생성 옵션 | | <kbd>&lt;c-y&gt;</kbd>: 풀 리퀘스트 URL을 클립보드에 복사
| `` <c-y> `` | 풀 리퀘스트 URL을 클립보드에 복사 | | <kbd>c</kbd>: 이름으로 체크아웃
| `` c `` | 이름으로 체크아웃 | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: 강제 체크아웃
| `` F `` | 강제 체크아웃 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: 브랜치 삭제
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: 현재 브랜치에 병합
| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: Fast-forward this branch from its upstream
| `` f `` | Fast-forward this branch from its upstream | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: 태그를 생성
| `` T `` | 태그를 생성 | | <kbd>g</kbd>: View reset options
| `` s `` | Sort order | | <kbd>R</kbd>: 브랜치 이름 변경
| `` g `` | View reset options | | <kbd>u</kbd>: Set/Unset upstream
| `` R `` | 브랜치 이름 변경 | | <kbd>&lt;enter&gt;</kbd>: 커밋 보기
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 커밋 보기 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 상태 ## 상태
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: 설정 파일 열기
| `` o `` | 설정 파일 열기 | Open file in default application. | <kbd>e</kbd>: 설정 파일 수정
| `` e `` | 설정 파일 수정 | Open file in external editor. | <kbd>u</kbd>: 업데이트 확인
| `` u `` | 업데이트 확인 | | <kbd>&lt;enter&gt;</kbd>: 최근에 사용한 저장소로 전환
| `` <enter> `` | 최근에 사용한 저장소로 전환 | | <kbd>a</kbd>: 모든 브랜치 로그 표시
| `` a `` | 모든 브랜치 로그 표시 | | </pre>
## 서브모듈 ## 서브모듈
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 서브모듈 이름을 클립보드에 복사
| `` <c-o> `` | 서브모듈 이름을 클립보드에 복사 | | <kbd>&lt;enter&gt;</kbd>: 서브모듈 열기
| `` <enter> `` | Enter | 서브모듈 열기 | <kbd>d</kbd>: 서브모듈 삭제
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: 서브모듈 업데이트
| `` u `` | Update | 서브모듈 업데이트 | <kbd>n</kbd>: 새로운 서브모듈 추가
| `` n `` | 새로운 서브모듈 추가 | | <kbd>e</kbd>: 서브모듈의 URL을 수정
| `` e `` | 서브모듈의 URL을 수정 | | <kbd>i</kbd>: 서브모듈 초기화
| `` i `` | Initialize | 서브모듈 초기화 | <kbd>b</kbd>: View bulk submodule options
| `` b `` | View bulk submodule options | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## 원격 ## 원격
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: 원격을 업데이트
| `` <enter> `` | View branches | | <kbd>n</kbd>: 새로운 Remote 추가
| `` n `` | 새로운 Remote 추가 | | <kbd>d</kbd>: Remote를 삭제
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: Remote를 수정
| `` e `` | Edit | Remote를 수정 | <kbd>/</kbd>: Filter the current view by text
| `` f `` | Fetch | 원격을 업데이트 | </pre>
| `` / `` | Filter the current view by text | |
## 원격 브랜치 ## 원격 브랜치
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 브랜치명을 클립보드에 복사
| `` <c-o> `` | 브랜치명을 클립보드에 복사 | | <kbd>&lt;space&gt;</kbd>: 체크아웃
| `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: 새 브랜치 생성
| `` n `` | 새 브랜치 생성 | | <kbd>M</kbd>: 현재 브랜치에 병합
| `` M `` | 현재 브랜치에 병합 | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
| `` r `` | 체크아웃된 브랜치를 이 브랜치에 리베이스 | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: 브랜치 삭제
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: Set as upstream of checked-out branch
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. | <kbd>g</kbd>: View reset options
| `` s `` | Sort order | | <kbd>&lt;enter&gt;</kbd>: 커밋 보기
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 커밋 보기 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 커밋 ## 커밋
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
| `` <c-o> `` | 커밋 해시를 클립보드에 복사 | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-r> `` | Reset cherry-picked (copied) commits selection | | <kbd>b</kbd>: Bisect 옵션 보기
| `` b `` | Bisect 옵션 보기 | | <kbd>s</kbd>: Squash down
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: Fixup commit
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: 커밋메시지 변경
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. | <kbd>R</kbd>: 에디터에서 커밋메시지 수정
| `` R `` | 에디터에서 커밋메시지 수정 | | <kbd>d</kbd>: 커밋 삭제
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: 커밋을 편집
| `` e `` | Edit (start interactive rebase) | 커밋을 편집 | <kbd>p</kbd>: Pick commit (when mid-rebase)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: Create fixup commit for this commit
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: Squash all 'fixup!' commits above selected commit (autosquash)
| `` p `` | Pick | Pick commit (when mid-rebase) | <kbd>&lt;c-j&gt;</kbd>: 커밋을 1개 아래로 이동
| `` F `` | Create fixup commit | Create fixup commit for this commit | <kbd>&lt;c-k&gt;</kbd>: 커밋을 1개 위로 이동
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits above selected commit (autosquash) | <kbd>v</kbd>: 커밋을 붙여넣기 (cherry-pick)
| `` <c-j> `` | 커밋을 1개 아래로 이동 | | <kbd>A</kbd>: Amend commit with staged changes
| `` <c-k> `` | 커밋을 1개 위로 이동 | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | 커밋을 붙여넣기 (cherry-pick) | | <kbd>t</kbd>: 커밋 되돌리기
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: Tag commit
| `` A `` | Amend | Amend commit with staged changes | <kbd>&lt;c-l&gt;</kbd>: 로그 메뉴 열기
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: 커밋 attribute 복사
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: 브라우저에서 커밋 열기
| `` <c-l> `` | 로그 메뉴 열기 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
| `` <space> `` | 체크아웃 | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: View reset options
| `` y `` | 커밋 attribute 복사 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: 커밋을 복사 (cherry-pick)
| `` o `` | 브라우저에서 커밋 열기 | | <kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | | <kbd>&lt;enter&gt;</kbd>: View selected item's files
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: 검색 시작
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | View selected item's files | |
| `` w `` | View worktree options | |
| `` / `` | 검색 시작 | |
## 커밋 파일 ## 커밋 파일
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 커밋한 파일명을 클립보드에 복사
| `` <c-o> `` | 파일명을 클립보드에 복사 | | <kbd>c</kbd>: Checkout file
| `` c `` | 체크아웃 | Checkout file | <kbd>d</kbd>: Discard this commit's changes to this file
| `` d `` | Remove | Discard this commit's changes to this file | <kbd>o</kbd>: 파일 닫기
| `` o `` | 파일 닫기 | Open file in default application. | <kbd>e</kbd>: 파일 편집
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Toggle file included in patch
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | 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. | <kbd>&lt;enter&gt;</kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
| `` a `` | Toggle all files included in patch | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: 파일 트리뷰로 전환
| `` <enter> `` | Enter file to add selected lines to the patch (or 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. | <kbd>/</kbd>: 검색 시작
| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | 검색 시작 | |
## 커밋메시지 ## 커밋메시지
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 확인
| `` <enter> `` | 확인 | | <kbd>&lt;esc&gt;</kbd>: 닫기
| `` <esc> `` | 닫기 | | </pre>
## 태그 ## 태그
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 체크아웃
| `` <space> `` | 체크아웃 | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: 태그 삭제
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: 태그를 push
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: 태그를 생성
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: View reset options
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: 커밋 보기
| `` <enter> `` | 커밋 보기 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## 파일 ## 파일
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 파일명을 클립보드에 복사
| `` <c-o> `` | 파일명을 클립보드에 복사 | | <kbd>d</kbd>: View 'discard changes' options
| `` <space> `` | Staged 전환 | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: Staged 전환
| `` <c-b> `` | 파일을 필터하기 (Staged/unstaged) | | <kbd>&lt;c-b&gt;</kbd>: 파일을 필터하기 (Staged/unstaged)
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: 커밋 변경내용
| `` c `` | 커밋 변경내용 | Commit staged changes. | <kbd>w</kbd>: Commit changes without pre-commit hook
| `` w `` | Commit changes without pre-commit hook | | <kbd>A</kbd>: 마지맛 커밋 수정
| `` A `` | 마지맛 커밋 수정 | | <kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | | <kbd>e</kbd>: 파일 편집
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: 파일 닫기
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: Ignore file
| `` o `` | 파일 닫기 | Open file in default application. | <kbd>r</kbd>: 파일 새로고침
| `` i `` | Ignore file | | <kbd>s</kbd>: 변경사항을 Stash
| `` r `` | 파일 새로고침 | | <kbd>S</kbd>: Stash 옵션 보기
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: 모든 변경을 Staged/unstaged으로 전환
| `` S `` | Stash 옵션 보기 | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: Stage individual hunks/lines for file, or collapse/expand for directory
| `` a `` | 모든 변경을 Staged/unstaged으로 전환 | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: View upstream reset options
| `` <enter> `` | Stage individual hunks/lines for file, or collapse/expand for directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: View reset options
| `` d `` | View 'discard changes' options | View options for discarding changes to the selected file. | <kbd>`</kbd>: 파일 트리뷰로 전환
| `` g `` | View upstream reset options | | <kbd>M</kbd>: Git mergetool를 열기
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: Fetch
| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: 검색 시작
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | Git mergetool를 열기 | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` / `` | 검색 시작 | |
## 확인 패널 ## 확인 패널
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 확인
| `` <enter> `` | 확인 | | <kbd>&lt;esc&gt;</kbd>: 닫기/취소
| `` <esc> `` | 닫기/취소 | | </pre>
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Sneltoetsen # Lazygit Sneltoetsen
@@ -6,359 +6,328 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globale sneltoetsen ## Globale sneltoetsen
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: Wissel naar een recente repo
| `` <c-r> `` | Wissel naar een recente repo | | <kbd>&lt;pgup&gt;</kbd>: Scroll naar beneden vanaf hoofdpaneel (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | | <kbd>&lt;pgdown&gt;</kbd>: Scroll naar beneden vanaf hoofdpaneel (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | | <kbd>@</kbd>: Open command log menu
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
| `` 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. | <kbd>:</kbd>: Voer aangepaste commando uit
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: Bekijk aangepaste patch opties
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: Bekijk merge/rebase opties
| `` : `` | Voer aangepaste commando uit | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: Verversen
| `` <c-p> `` | Bekijk aangepaste patch opties | | <kbd>+</kbd>: Volgende scherm modus (normaal/half/groot)
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: Vorige scherm modus
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: Open menu
| `` + `` | Volgende scherm modus (normaal/half/groot) | | <kbd>&lt;c-s&gt;</kbd>: Bekijk scoping opties
| `` _ `` | Vorige scherm modus | | <kbd>W</kbd>: Open diff menu
| `` ? `` | Open menu | | <kbd>&lt;c-e&gt;</kbd>: Open diff menu
| `` <c-s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
| `` W `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: Ongedaan maken (via reflog) (experimenteel)
| `` <c-e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: Redo (via reflog) (experimenteel)
| `` q `` | Quit | | <kbd>P</kbd>: Push
| `` <esc> `` | Annuleren | | <kbd>p</kbd>: Pull
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## Lijstpaneel navigatie ## Lijstpaneel navigatie
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: Vorige pagina
| `` , `` | Vorige pagina | | <kbd>.</kbd>: Volgende pagina
| `` . `` | Volgende pagina | | <kbd>&lt;</kbd>: Scroll naar boven
| `` < `` | Scroll naar boven | | <kbd>&gt;</kbd>: Scroll naar beneden
| `` > `` | Scroll naar beneden | | <kbd>/</kbd>: Start met zoeken
| `` v `` | Toggle drag selecteer | | <kbd>H</kbd>: Scroll left
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: Scroll right
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: Volgende tabblad
| `` / `` | Start met zoeken | | <kbd>[</kbd>: Vorige tabblad
| `` H `` | Scroll left | | </pre>
| `` L `` | Scroll right | |
| `` ] `` | Volgende tabblad | |
| `` [ `` | Vorige tabblad | |
## Bestanden ## Bestanden
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer de bestandsnaam naar het klembord
| `` <c-o> `` | Kopieer de bestandsnaam naar het klembord | | <kbd>d</kbd>: Bekijk 'veranderingen ongedaan maken' opties
| `` <space> `` | Toggle staged | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: Toggle staged
| `` <c-b> `` | Filter files by status | | <kbd>&lt;c-b&gt;</kbd>: Filter files by status
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: Commit veranderingen
| `` c `` | Commit veranderingen | Commit staged changes. | <kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
| `` w `` | Commit veranderingen zonder pre-commit hook | | <kbd>A</kbd>: Wijzig laatste commit
| `` A `` | Wijzig laatste commit | | <kbd>C</kbd>: Commit veranderingen met de git editor
| `` C `` | Commit veranderingen met de git editor | | <kbd>e</kbd>: Verander bestand
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: Open bestand
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: Ignore or exclude file
| `` o `` | Open bestand | Open file in default application. | <kbd>r</kbd>: Refresh bestanden
| `` i `` | Ignore or exclude file | | <kbd>s</kbd>: Stash-bestanden
| `` r `` | Refresh bestanden | | <kbd>S</kbd>: Bekijk stash opties
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: Toggle staged alle
| `` S `` | Bekijk stash opties | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: Stage individuele hunks/lijnen
| `` a `` | Toggle staged alle | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: Bekijk upstream reset opties
| `` <enter> `` | Stage individuele hunks/lijnen | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: Bekijk reset opties
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. | <kbd>`</kbd>: Toggle bestandsboom weergave
| `` g `` | Bekijk upstream reset opties | | <kbd>M</kbd>: Open external merge tool (git mergetool)
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: Fetch
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: Start met zoeken
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` / `` | Start met zoeken | |
## Bevestigingspaneel ## Bevestigingspaneel
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Bevestig
| `` <enter> `` | Bevestig | | <kbd>&lt;esc&gt;</kbd>: Sluiten
| `` <esc> `` | Sluiten | | </pre>
## Branches ## Branches
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer branch name naar klembord
| `` <c-o> `` | Kopieer branch name naar klembord | | <kbd>i</kbd>: Laat git-flow opties zien
| `` i `` | Laat git-flow opties zien | | <kbd>&lt;space&gt;</kbd>: Uitchecken
| `` <space> `` | Uitchecken | Checkout selected item. | <kbd>n</kbd>: Nieuwe branch
| `` n `` | Nieuwe branch | | <kbd>o</kbd>: Maak een pull-request
| `` o `` | Maak een pull-request | | <kbd>O</kbd>: Bekijk opties voor pull-aanvraag
| `` O `` | Bekijk opties voor pull-aanvraag | | <kbd>&lt;c-y&gt;</kbd>: Kopieer de URL van het pull-verzoek naar het klembord
| `` <c-y> `` | Kopieer de URL van het pull-verzoek naar het klembord | | <kbd>c</kbd>: Uitchecken bij naam
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: Forceer checkout
| `` F `` | Forceer checkout | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: Verwijder branch
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: Rebase branch
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: Merge in met huidige checked out branch
| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: Fast-forward deze branch vanaf zijn upstream
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: Creëer tag
| `` T `` | Creëer tag | | <kbd>g</kbd>: Bekijk reset opties
| `` s `` | Sort order | | <kbd>R</kbd>: Hernoem branch
| `` g `` | Bekijk reset opties | | <kbd>u</kbd>: Set/Unset upstream
| `` R `` | Hernoem branch | | <kbd>&lt;enter&gt;</kbd>: Bekijk commits
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | Bekijk commits | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Commit bericht ## Commit bericht
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Bevestig
| `` <enter> `` | Bevestig | | <kbd>&lt;esc&gt;</kbd>: Sluiten
| `` <esc> `` | Sluiten | | </pre>
## Commit bestanden ## Commit bestanden
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer de vastgelegde bestandsnaam naar het klembord
| `` <c-o> `` | Kopieer de bestandsnaam naar het klembord | | <kbd>c</kbd>: Bestand uitchecken
| `` c `` | Uitchecken | Bestand uitchecken | <kbd>d</kbd>: Uitsluit deze commit zijn veranderingen aan dit bestand
| `` d `` | Remove | Uitsluit deze commit zijn veranderingen aan dit bestand | <kbd>o</kbd>: Open bestand
| `` o `` | Open bestand | Open file in default application. | <kbd>e</kbd>: Verander bestand
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Toggle bestand inbegrepen in patch
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>&lt;enter&gt;</kbd>: Enter bestand om geselecteerde regels toe te voegen aan de patch
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: Toggle bestandsboom weergave
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | <kbd>/</kbd>: Start met zoeken
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | Start met zoeken | |
## Commits ## Commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer commit SHA naar klembord
| `` <c-o> `` | Kopieer commit hash naar klembord | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (gekopieerde) commits selectie
| `` <c-r> `` | Reset cherry-picked (gekopieerde) commits selectie | | <kbd>b</kbd>: View bisect options
| `` b `` | View bisect options | | <kbd>s</kbd>: Squash beneden
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: Fixup commit
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: Hernoem commit
| `` r `` | Hernoem commit | Reword the selected commit's message. | <kbd>R</kbd>: Hernoem commit met editor
| `` R `` | Hernoem commit met editor | | <kbd>d</kbd>: Verwijder commit
| `` d `` | Verwijder commit | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: Wijzig commit
| `` e `` | Edit (start interactive rebase) | Wijzig commit | <kbd>p</kbd>: Kies commit (wanneer midden in rebase)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: Creëer fixup commit
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: Squash bovenstaande commits
| `` p `` | Pick | Kies commit (wanneer midden in rebase) | <kbd>&lt;c-j&gt;</kbd>: Verplaats commit 1 naar beneden
| `` F `` | Creëer fixup commit | Creëer fixup commit | <kbd>&lt;c-k&gt;</kbd>: Verplaats commit 1 naar boven
| `` S `` | Apply fixup commits | Squash bovenstaande commits | <kbd>v</kbd>: Plak commits (cherry-pick)
| `` <c-j> `` | Verplaats commit 1 naar beneden | | <kbd>A</kbd>: Wijzig commit met staged veranderingen
| `` <c-k> `` | Verplaats commit 1 naar boven | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | Plak commits (cherry-pick) | | <kbd>t</kbd>: Commit ongedaan maken
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: Tag commit
| `` A `` | Amend | Wijzig commit met staged veranderingen | <kbd>&lt;c-l&gt;</kbd>: Open log menu
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: Copy commit attribute
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: Open commit in browser
| `` <c-l> `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: Creëer nieuwe branch van commit
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: Bekijk reset opties
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: Kopieer commit (cherry-pick)
| `` o `` | Open commit in browser | | <kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
| `` n `` | Creëer nieuwe branch van commit | | <kbd>&lt;enter&gt;</kbd>: Bekijk gecommite bestanden
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Start met zoeken
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Bekijk gecommite bestanden | |
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | |
## Menu ## Menu
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Uitvoeren
| `` <enter> `` | Uitvoeren | | <kbd>&lt;esc&gt;</kbd>: Sluiten
| `` <esc> `` | Sluiten | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Mergen ## Mergen
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: Verander bestand
| `` <space> `` | Kies stuk | | <kbd>o</kbd>: Open bestand
| `` b `` | Kies beide stukken | | <kbd>&lt;left&gt;</kbd>: Selecteer voorgaand conflict
| `` <up> `` | Selecteer bovenste hunk | | <kbd>&lt;right&gt;</kbd>: Selecteer volgende conflict
| `` <down> `` | Selecteer onderste hunk | | <kbd>&lt;up&gt;</kbd>: Selecteer bovenste hunk
| `` <left> `` | Selecteer voorgaand conflict | | <kbd>&lt;down&gt;</kbd>: Selecteer onderste hunk
| `` <right> `` | Selecteer volgende conflict | | <kbd>z</kbd>: Ongedaan maken
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. | <kbd>M</kbd>: Open external merge tool (git mergetool)
| `` e `` | Verander bestand | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Kies stuk
| `` o `` | Open bestand | Open file in default application. | <kbd>b</kbd>: Kies beide stukken
| `` M `` | Open external merge tool | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: Ga terug naar het bestanden paneel
| `` <esc> `` | Ga terug naar het bestanden paneel | | </pre>
## Normaal ## Normaal
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: Scroll omlaag (fn+up)
| `` mouse wheel down (fn+up) `` | Scroll omlaag | | <kbd>mouse wheel up</kbd>: Scroll omhoog (fn+down)
| `` mouse wheel up (fn+down) `` | Scroll omhoog | | </pre>
## Patch bouwen ## Patch bouwen
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk
| `` <left> `` | Selecteer de vorige hunk | | <kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk
| `` <right> `` | Selecteer de volgende hunk | | <kbd>v</kbd>: Toggle drag selecteer
| `` v `` | Toggle drag selecteer | | <kbd>V</kbd>: Toggle drag selecteer
| `` a `` | Toggle selecteer hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle selecteer hunk
| `` <c-o> `` | Copy selected text to clipboard | | <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
| `` o `` | Open bestand | Open file in default application. | <kbd>o</kbd>: Open bestand
| `` e `` | Verander bestand | Open file in external editor. | <kbd>e</kbd>: Verander bestand
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | | <kbd>&lt;space&gt;</kbd>: Voeg toe/verwijder lijn(en) in patch
| `` <esc> `` | Sluit lijn-bij-lijn modus | | <kbd>&lt;esc&gt;</kbd>: Sluit lijn-bij-lijn modus
| `` / `` | Start met zoeken | | <kbd>/</kbd>: Start met zoeken
</pre>
## Reflog ## Reflog
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer commit SHA naar klembord
| `` <c-o> `` | Kopieer commit hash naar klembord | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Open commit in browser | | <kbd>n</kbd>: Creëer nieuwe branch van commit
| `` n `` | Creëer nieuwe branch van commit | | <kbd>g</kbd>: Bekijk reset opties
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Kopieer commit (cherry-pick)
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
| `` <c-r> `` | Reset cherry-picked (gekopieerde) commits selectie | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (gekopieerde) commits selectie
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: Bekijk commits
| `` <enter> `` | Bekijk commits | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Remote branches ## Remote branches
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer branch name naar klembord
| `` <c-o> `` | Kopieer branch name naar klembord | | <kbd>&lt;space&gt;</kbd>: Uitchecken
| `` <space> `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: Nieuwe branch
| `` n `` | Nieuwe branch | | <kbd>M</kbd>: Merge in met huidige checked out branch
| `` M `` | Merge in met huidige checked out branch | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: Rebase branch
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: Verwijder branch
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: Stel in als upstream van uitgecheckte branch
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch | <kbd>g</kbd>: Bekijk reset opties
| `` s `` | Sort order | | <kbd>&lt;enter&gt;</kbd>: Bekijk commits
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | Bekijk commits | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Remotes ## Remotes
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: Fetch remote
| `` <enter> `` | View branches | | <kbd>n</kbd>: Voeg een nieuwe remote toe
| `` n `` | Voeg een nieuwe remote toe | | <kbd>d</kbd>: Verwijder remote
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: Wijzig remote
| `` e `` | Edit | Wijzig remote | <kbd>/</kbd>: Filter the current view by text
| `` f `` | Fetch | Fetch remote | </pre>
| `` / `` | Filter the current view by text | |
## Staging ## Staging
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Selecteer de vorige hunk
| `` <left> `` | Selecteer de vorige hunk | | <kbd>&lt;right&gt;</kbd>: Selecteer de volgende hunk
| `` <right> `` | Selecteer de volgende hunk | | <kbd>v</kbd>: Toggle drag selecteer
| `` v `` | Toggle drag selecteer | | <kbd>V</kbd>: Toggle drag selecteer
| `` a `` | Toggle selecteer hunk | Toggle hunk selection mode. | <kbd>a</kbd>: Toggle selecteer hunk
| `` <c-o> `` | Copy selected text to clipboard | | <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged | <kbd>o</kbd>: Open bestand
| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: Verander bestand
| `` o `` | Open bestand | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: Ga terug naar het bestanden paneel
| `` e `` | Verander bestand | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: Ga naar een ander paneel
| `` <esc> `` | Ga terug naar het bestanden paneel | | <kbd>&lt;space&gt;</kbd>: Toggle lijnen staged / unstaged
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: Verwijdert change (git reset)
| `` E `` | Edit hunk | Edit selected hunk in external editor. | <kbd>E</kbd>: Edit hunk
| `` c `` | Commit veranderingen | Commit staged changes. | <kbd>c</kbd>: Commit veranderingen
| `` w `` | Commit veranderingen zonder pre-commit hook | | <kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
| `` C `` | Commit veranderingen met de git editor | | <kbd>C</kbd>: Commit veranderingen met de git editor
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: Start met zoeken
| `` / `` | Start met zoeken | | </pre>
## Stash ## Stash
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Toepassen
| `` <space> `` | Toepassen | Apply the stash entry to your working directory. | <kbd>g</kbd>: Pop
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: Laten vallen
| `` d `` | Laten vallen | Remove the stash entry from the stash list. | <kbd>n</kbd>: Nieuwe branch
| `` n `` | Nieuwe branch | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Rename stash
| `` r `` | Rename stash | | <kbd>&lt;enter&gt;</kbd>: Bekijk gecommite bestanden
| `` <enter> `` | Bekijk gecommite bestanden | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Status ## Status
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: Open config bestand
| `` o `` | Open config bestand | Open file in default application. | <kbd>e</kbd>: Verander config bestand
| `` e `` | Verander config bestand | Open file in external editor. | <kbd>u</kbd>: Check voor updates
| `` u `` | Check voor updates | | <kbd>&lt;enter&gt;</kbd>: Wissel naar een recente repo
| `` <enter> `` | Wissel naar een recente repo | | <kbd>a</kbd>: Alle logs van de branch laten zien
| `` a `` | Alle logs van de branch laten zien | | </pre>
## Sub-commits ## Sub-commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer commit SHA naar klembord
| `` <c-o> `` | Kopieer commit hash naar klembord | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Uitchecken | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Open commit in browser | | <kbd>n</kbd>: Creëer nieuwe branch van commit
| `` n `` | Creëer nieuwe branch van commit | | <kbd>g</kbd>: Bekijk reset opties
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Kopieer commit (cherry-pick)
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
| `` <c-r> `` | Reset cherry-picked (gekopieerde) commits selectie | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (gekopieerde) commits selectie
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: Bekijk gecommite bestanden
| `` <enter> `` | Bekijk gecommite bestanden | | <kbd>/</kbd>: Start met zoeken
| `` w `` | View worktree options | | </pre>
| `` / `` | Start met zoeken | |
## Submodules ## Submodules
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Kopieer submodule naam naar klembord
| `` <c-o> `` | Kopieer submodule naam naar klembord | | <kbd>&lt;enter&gt;</kbd>: Enter submodule
| `` <enter> `` | Enter | Enter submodule | <kbd>d</kbd>: Remove submodule
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: Update submodule
| `` u `` | Update | Update selected submodule. | <kbd>n</kbd>: Voeg nieuwe submodule toe
| `` n `` | Voeg nieuwe submodule toe | | <kbd>e</kbd>: Update submodule URL
| `` e `` | Update submodule URL | | <kbd>i</kbd>: Initialiseer submodule
| `` i `` | Initialize | Initialiseer submodule | <kbd>b</kbd>: Bekijk bulk submodule opties
| `` b `` | Bekijk bulk submodule opties | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Tags ## Tags
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Uitchecken
| `` <space> `` | Uitchecken | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: Verwijder tag
| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: Push tag
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: Creëer tag
| `` P `` | Push tag | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: Bekijk reset opties
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: Bekijk commits
| `` <enter> `` | Bekijk commits | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
+290 -321
View File
@@ -1,364 +1,333 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Skróty klawiszowe # Lazygit Keybindings
_Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globalne skróty klawiszowe ## Globalne
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: Switch to a recent repo
| `` <c-r> `` | Przełącz na ostatnie repozytorium | | <kbd>&lt;pgup&gt;</kbd>: Scroll up main panel (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | Przewiń główne okno w górę | | <kbd>&lt;pgdown&gt;</kbd>: Scroll down main panel (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | Przewiń główne okno w dół | | <kbd>@</kbd>: Open command log menu
| `` @ `` | Pokaż opcje dziennika poleceń | Pokaż opcje dla dziennika poleceń, np. pokazywanie/ukrywanie dziennika poleceń i skupienie na dzienniku poleceń. | <kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
| `` P `` | Wypchnij | Wypchnij bieżącą gałąź do jej gałęzi nadrzędnej. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. | <kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
| `` p `` | Pociągnij | Pociągnij zmiany z zdalnego dla bieżącej gałęzi. Jeśli nie skonfigurowano gałęzi nadrzędnej, zostaniesz poproszony o skonfigurowanie gałęzi nadrzędnej. | <kbd>:</kbd>: Wykonaj własną komendę
| `` } `` | Zwiększ rozmiar kontekstu w widoku różnic | Zwiększ ilość kontekstu pokazywanego wokół zmian w widoku różnic. | <kbd>&lt;c-p&gt;</kbd>: View custom patch options
| `` { `` | Zmniejsz rozmiar kontekstu w widoku różnic | Zmniejsz ilość kontekstu pokazywanego wokół zmian w widoku różnic. | <kbd>m</kbd>: Widok scalenia/opcje zmiany bazy
| `` : `` | Wykonaj polecenie niestandardowe | Wyświetl monit, w którym możesz wprowadzić polecenie powłoki do wykonania. Nie należy mylić z wcześniej skonfigurowanymi poleceniami niestandardowymi. | <kbd>R</kbd>: Odśwież
| `` <c-p> `` | Wyświetl opcje niestandardowej łatki | | <kbd>+</kbd>: Next screen mode (normal/half/fullscreen)
| `` m `` | Pokaż opcje scalania/rebase | Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase. | <kbd>_</kbd>: Prev screen mode
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. | <kbd>?</kbd>: Open menu
| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | | <kbd>&lt;c-s&gt;</kbd>: View filter-by-path options
| `` _ `` | Poprzedni tryb ekranu | | <kbd>W</kbd>: Open diff menu
| `` ? `` | Otwórz menu przypisań klawiszy | | <kbd>&lt;c-e&gt;</kbd>: Open diff menu
| `` <c-s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. | <kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
| `` W `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. | <kbd>z</kbd>: Undo
| `` <c-e> `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. | <kbd>&lt;c-z&gt;</kbd>: Redo
| `` q `` | Wyjdź | | <kbd>P</kbd>: Push
| `` <esc> `` | Anuluj | | <kbd>p</kbd>: Pull
| `` <c-w> `` | Przełącz białe znaki | Przełącz czy zmiany białych znaków są pokazywane w widoku różnic. | </pre>
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
| `` <c-z> `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
## Nawigacja panelu listy ## List panel navigation
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: Previous page
| `` , `` | Poprzednia strona | | <kbd>.</kbd>: Next page
| `` . `` | Następna strona | | <kbd>&lt;</kbd>: Scroll to top
| `` < `` | Przewiń do góry | | <kbd>&gt;</kbd>: Scroll to bottom
| `` > `` | Przewiń do dołu | | <kbd>/</kbd>: Search the current view by text
| `` v `` | Przełącz zaznaczenie zakresu | | <kbd>H</kbd>: Scroll left
| `` <s-down> `` | Zaznacz zakres w dół | | <kbd>L</kbd>: Scroll right
| `` <s-up> `` | Zaznacz zakres w górę | | <kbd>]</kbd>: Next tab
| `` / `` | Szukaj w bieżącym widoku po tekście | | <kbd>[</kbd>: Previous tab
| `` H `` | Przewiń w lewo | | </pre>
| `` L `` | Przewiń w prawo | |
| `` ] `` | Następna zakładka | | ## Commit summary
| `` [ `` | Poprzednia zakładka | |
<pre>
<kbd>&lt;enter&gt;</kbd>: Potwierdź
<kbd>&lt;esc&gt;</kbd>: Zamknij
</pre>
## Commity ## Commity
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Kopiuj hash commita do schowka | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-r> `` | Resetuj wybrane (cherry-picked) commity | | <kbd>b</kbd>: View bisect options
| `` b `` | Zobacz opcje bisect | | <kbd>s</kbd>: Ściśnij
| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. | <kbd>f</kbd>: Napraw commit
| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. | <kbd>r</kbd>: Zmień nazwę commita
| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. | <kbd>R</kbd>: Zmień nazwę commita w edytorze
| `` R `` | Przeformułuj za pomocą edytora | | <kbd>d</kbd>: Usuń commit
| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. | <kbd>e</kbd>: Edytuj commit
| `` e `` | Edytuj (rozpocznij interaktywne rebazowanie) | Edytuj wybrany commit. Użyj tego, aby rozpocząć interaktywne rebazowanie od wybranego commita. Podczas trwania rebazowania, to oznaczy wybrany commit do edycji, co oznacza, że po kontynuacji rebazowania, rebazowanie zostanie wstrzymane na wybranym commicie, aby umożliwić wprowadzenie zmian. | <kbd>p</kbd>: Wybierz commit (podczas zmiany bazy)
| `` i `` | Rozpocznij interaktywny rebase | Rozpocznij interaktywny rebase dla commitów na twoim branchu. To będzie zawierać wszystkie commity od HEAD do pierwszego commita scalenia lub commita głównego brancha. <kbd>F</kbd>: Utwórz commit naprawczy dla tego commita
Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita, naciśnij `e`. | <kbd>S</kbd>: Spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
| `` p `` | Wybierz | Oznacz wybrany commit do wybrania (podczas rebazowania). Oznacza to, że commit zostanie zachowany po kontynuacji rebazowania. | <kbd>&lt;c-j&gt;</kbd>: Przenieś commit 1 w dół
| `` F `` | Utwórz commit fixup | Utwórz commit 'fixup!' dla wybranego commita. Później możesz nacisnąć `S` na tym samym commicie, aby zastosować wszystkie powyższe commity fixup. | <kbd>&lt;c-k&gt;</kbd>: Przenieś commit 1 w górę
| `` S `` | Zastosuj commity fixup | Scal wszystkie commity 'fixup!', albo powyżej wybranego commita, albo wszystkie w bieżącej gałęzi (autosquash). | <kbd>v</kbd>: Wklej commity (przebieranie)
| `` <c-j> `` | Przesuń commit w dół | | <kbd>A</kbd>: Popraw commit zmianami z poczekalni
| `` <c-k> `` | Przesuń commit w górę | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | Wklej (cherry-pick) | | <kbd>t</kbd>: Odwróć commit
| `` B `` | Oznacz jako bazowy commit dla rebase | Wybierz bazowy commit dla następnego rebase. Kiedy robisz rebase na branch, tylko commity powyżej bazowego commita zostaną przeniesione. Używa to polecenia `git rebase --onto`. | <kbd>T</kbd>: Tag commit
| `` A `` | Popraw | Popraw commit ze zmianami zatwierdzonymi. Jeśli wybrany commit jest commit HEAD, to wykona `git commit --amend`. W przeciwnym razie commit zostanie poprawiony za pomocą rebazowania. | <kbd>&lt;c-l&gt;</kbd>: Open log menu
| `` a `` | Popraw atrybut commita | Ustaw/Resetuj autora commita lub ustaw współautora. | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` t `` | Cofnij | Utwórz commit cofający dla wybranego commita, który stosuje zmiany wybranego commita w odwrotnej kolejności. | <kbd>y</kbd>: Copy commit attribute
| `` T `` | Otaguj commit | Utwórz nowy tag wskazujący na wybrany commit. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | <kbd>o</kbd>: Open commit in browser
| `` <c-l> `` | Zobacz opcje logów | Zobacz opcje dla logów commitów, np. zmiana kolejności sortowania, ukrywanie grafu gita, pokazywanie całego grafu gita. | <kbd>n</kbd>: Create new branch off of commit
| `` <space> `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | <kbd>g</kbd>: Wyświetl opcje resetu
| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | <kbd>c</kbd>: Kopiuj commit (przebieranie)
| `` o `` | Otwórz commit w przeglądarce | | <kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
| `` n `` | Utwórz nową gałąź z commita | | <kbd>&lt;enter&gt;</kbd>: Przeglądaj pliki commita
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | <kbd>/</kbd>: Search the current view by text
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | </pre>
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` <enter> `` | Wyświetl pliki | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Drzewa pracy ## Confirmation panel
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Potwierdź
| `` n `` | Nowe drzewo pracy | | <kbd>&lt;esc&gt;</kbd>: Zamknij
| `` <space> `` | Przełącz | Przełącz do wybranego drzewa pracy. | </pre>
| `` o `` | Otwórz w edytorze | |
| `` d `` | Usuń | Usuń wybrane drzewo pracy. To usunie zarówno katalog drzewa pracy, jak i metadane o drzewie pracy w katalogu .git. |
| `` / `` | Filtruj bieżący widok po tekście | |
## Główny panel (budowanie łatki) ## Local branches
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy branch name to clipboard
| `` <left> `` | Idź do poprzedniego fragmentu | | <kbd>i</kbd>: Show git-flow options
| `` <right> `` | Idź do następnego fragmentu | | <kbd>&lt;space&gt;</kbd>: Przełącz
| `` v `` | Przełącz zaznaczenie zakresu | | <kbd>n</kbd>: Nowa gałąź
| `` a `` | Zaznacz fragment | Przełącz tryb zaznaczania fragmentu. | <kbd>o</kbd>: Utwórz żądanie pobrania
| `` <c-o> `` | Kopiuj zaznaczony tekst do schowka | | <kbd>O</kbd>: Utwórz opcje żądania ściągnięcia
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | <kbd>&lt;c-y&gt;</kbd>: Skopiuj adres URL żądania pobrania do schowka
| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. | <kbd>c</kbd>: Przełącz używając nazwy
| `` <space> `` | Przełącz linie w łatce | | <kbd>F</kbd>: Wymuś przełączenie
| `` <esc> `` | Wyjdź z budowniczego niestandardowej łatki | | <kbd>d</kbd>: Usuń gałąź
| `` / `` | Szukaj w bieżącym widoku po tekście | | <kbd>r</kbd>: Zmiana bazy gałęzi
<kbd>M</kbd>: Scal do obecnej gałęzi
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: Create tag
<kbd>g</kbd>: Wyświetl opcje resetu
<kbd>R</kbd>: Rename branch
<kbd>u</kbd>: Set/Unset upstream
<kbd>&lt;enter&gt;</kbd>: View commits
<kbd>/</kbd>: Filter the current view by text
</pre>
## Lokalne gałęzie ## Main panel (patch building)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Poprzedni kawałek
| `` <c-o> `` | Kopiuj nazwę gałęzi do schowka | | <kbd>&lt;right&gt;</kbd>: Następny kawałek
| `` i `` | Pokaż opcje git-flow | | <kbd>v</kbd>: Toggle drag select
| `` <space> `` | Przełącz | Przełącz wybrany element. | <kbd>V</kbd>: Toggle drag select
| `` n `` | Nowa gałąź | | <kbd>a</kbd>: Toggle select hunk
| `` o `` | Utwórz żądanie ściągnięcia | | <kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
| `` O `` | Zobacz opcje tworzenia pull requesta | | <kbd>o</kbd>: Otwórz plik
| `` <c-y> `` | Kopiuj adres URL żądania ściągnięcia do schowka | | <kbd>e</kbd>: Edytuj plik
| `` c `` | Przełącz według nazwy | Przełącz według nazwy. W polu wprowadzania możesz wpisać '-' aby przełączyć się na ostatnią gałąź. | <kbd>&lt;space&gt;</kbd>: Add/Remove line(s) to patch
| `` F `` | Wymuś przełączenie | Wymuś przełączenie wybranej gałęzi. To spowoduje odrzucenie wszystkich lokalnych zmian w drzewie roboczym przed przełączeniem na wybraną gałąź. | <kbd>&lt;esc&gt;</kbd>: Wyście z trybu "linia po linii"
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnej/odległej gałęzi. | <kbd>/</kbd>: Search the current view by text
| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. | </pre>
| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. |
| `` f `` | Szybkie przewijanie | Szybkie przewijanie wybranej gałęzi z jej źródła. |
| `` T `` | Nowy tag | |
| `` s `` | Kolejność sortowania | |
| `` g `` | Reset | |
| `` R `` | Zmień nazwę gałęzi | |
| `` u `` | Pokaż opcje upstream | Pokaż opcje dotyczące upstream gałęzi, np. ustawianie/usuwanie upstream i resetowanie do upstream. |
| `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
## Menu ## Menu
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Wykonaj
| `` <enter> `` | Wykonaj | | <kbd>&lt;esc&gt;</kbd>: Zamknij
| `` <esc> `` | Zamknij | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filtruj bieżący widok po tekście | | </pre>
## Panel główny (normalny)
| Key | Action | Info |
|-----|--------|-------------|
| `` mouse wheel down (fn+up) `` | Przewiń w dół | |
| `` mouse wheel up (fn+down) `` | Przewiń w górę | |
## Panel główny (scalanie)
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Wybierz fragment | |
| `` b `` | Wybierz wszystkie fragmenty | |
| `` <up> `` | Poprzedni fragment | |
| `` <down> `` | Następny fragment | |
| `` <left> `` | Poprzedni konflikt | |
| `` <right> `` | Następny konflikt | |
| `` z `` | Cofnij | Cofnij ostatnie rozwiązanie konfliktu scalania. |
| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. |
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. |
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
| `` <esc> `` | Wróć do panelu plików | |
## Panel główny (zatwierdzanie)
| Key | Action | Info |
|-----|--------|-------------|
| `` <left> `` | Idź do poprzedniego fragmentu | |
| `` <right> `` | Idź do następnego fragmentu | |
| `` v `` | Przełącz zaznaczenie zakresu | |
| `` a `` | Zaznacz fragment | Przełącz tryb zaznaczania fragmentu. |
| `` <c-o> `` | Kopiuj zaznaczony tekst do schowka | |
| `` <space> `` | Zatwierdź | Przełącz zaznaczenie zatwierdzone/niezatwierdzone. |
| `` d `` | Odrzuć | Gdy zaznaczona jest niezatwierdzona zmiana, odrzuć ją używając `git reset`. Gdy zaznaczona jest zatwierdzona zmiana, cofnij zatwierdzenie. |
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. |
| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. |
| `` <esc> `` | Wróć do panelu plików | |
| `` <tab> `` | Przełącz widok | Przełącz na inny widok (zatwierdzone/niezatwierdzone zmiany). |
| `` E `` | Edytuj fragment | Edytuj wybrany fragment w zewnętrznym edytorze. |
| `` c `` | Commit | Zatwierdź zmiany zatwierdzone. |
| `` w `` | Zatwierdź zmiany bez hooka pre-commit | |
| `` C `` | Zatwierdź zmiany używając edytora git | |
| `` <c-f> `` | Znajdź bazowy commit do poprawki | Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Panel potwierdzenia
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Potwierdź | |
| `` <esc> `` | Zamknij/Anuluj | |
## Pliki ## Pliki
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy the file name to the clipboard
| `` <c-o> `` | Kopiuj ścieżkę do schowka | | <kbd>d</kbd>: Pokaż opcje porzucania zmian
| `` <space> `` | Zatwierdź | Przełącz zatwierdzenie dla wybranego pliku. | <kbd>&lt;space&gt;</kbd>: Przełącz stan poczekalni
| `` <c-b> `` | Filtruj pliki według statusu | | <kbd>&lt;c-b&gt;</kbd>: Filter files by status
| `` y `` | Kopiuj do schowka | | <kbd>c</kbd>: Zatwierdź zmiany
| `` c `` | Commit | Zatwierdź zmiany zatwierdzone. | <kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
| `` w `` | Zatwierdź zmiany bez hooka pre-commit | | <kbd>A</kbd>: Zmień ostatni commit
| `` A `` | Popraw ostatni commit | | <kbd>C</kbd>: Zatwierdź zmiany używając edytora
| `` C `` | Zatwierdź zmiany używając edytora git | | <kbd>e</kbd>: Edytuj plik
| `` <c-f> `` | Znajdź bazowy commit do poprawki | Znajdź commit, na którym opierają się Twoje obecne zmiany, w celu poprawienia/zmiany commita. To pozwala Ci uniknąć przeglądania commitów w Twojej gałęzi jeden po drugim, aby zobaczyć, który commit powinien być poprawiony/zmieniony. Zobacz dokumentację: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: Otwórz plik
| `` e `` | Edytuj | Otwórz plik w zewnętrznym edytorze. | <kbd>i</kbd>: Ignore or exclude file
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | <kbd>r</kbd>: Odśwież pliki
| `` i `` | Ignoruj lub wyklucz plik | | <kbd>s</kbd>: Przechowaj zmiany
| `` r `` | Odśwież pliki | | <kbd>S</kbd>: Wyświetl opcje schowka
| `` s `` | Schowaj | Schowaj wszystkie zmiany. Dla innych wariantów schowania, użyj klawisza wyświetlania opcji schowka. | <kbd>a</kbd>: Przełącz stan poczekalni wszystkich
| `` S `` | Wyświetl opcje schowka | Wyświetl opcje schowka (np. schowaj wszystko, schowaj zatwierdzone, schowaj niezatwierdzone). | <kbd>&lt;enter&gt;</kbd>: Zatwierdź pojedyncze linie
| `` a `` | Zatwierdź wszystko | Przełącz zatwierdzenie/odznaczenie dla wszystkich plików w drzewie roboczym. | <kbd>g</kbd>: View upstream reset options
| `` <enter> `` | Zatwierdź linie / Zwiń katalog | Jeśli wybrany element jest plikiem, skup się na widoku zatwierdzania, aby móc zatwierdzać poszczególne fragmenty/linie. Jeśli wybrany element jest katalogiem, zwiń/rozwiń go. | <kbd>D</kbd>: Wyświetl opcje resetu
| `` d `` | Odrzuć | Wyświetl opcje odrzucania zmian w wybranym pliku. | <kbd>`</kbd>: Toggle file tree view
| `` g `` | Pokaż opcje resetowania do upstream | | <kbd>M</kbd>: Open external merge tool (git mergetool)
| `` D `` | Reset | Wyświetl opcje resetu dla drzewa roboczego (np. zniszczenie drzewa roboczego). | <kbd>f</kbd>: Pobierz
| `` ` `` | Przełącz widok drzewa plików | Przełącz widok plików między płaskim a drzewem. Płaski układ pokazuje wszystkie ścieżki plików na jednej liście, układ drzewa grupuje pliki według katalogów. | <kbd>/</kbd>: Search the current view by text
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | </pre>
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
| `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Pliki commita ## Pliki commita
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy the committed file name to the clipboard
| `` <c-o> `` | Kopiuj ścieżkę do schowka | | <kbd>c</kbd>: Plik wybierania
| `` c `` | Przełącz | Przełącz plik. Zastępuje plik w twoim drzewie roboczym wersją z wybranego commita. | <kbd>d</kbd>: Porzuć zmiany commita dla tego pliku
| `` d `` | Usuń | Odrzuć zmiany w tym pliku z tego commita. Uruchamia interaktywny rebase w tle, więc możesz otrzymać konflikt scalania, jeśli późniejszy commit również zmienia ten plik. | <kbd>o</kbd>: Otwórz plik
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. | <kbd>e</kbd>: Edytuj plik
| `` e `` | Edytuj | Otwórz plik w zewnętrznym edytorze. | <kbd>&lt;space&gt;</kbd>: Toggle file included in patch
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | Przełącz plik włączony w łatkę | Przełącz, czy plik jest włączony w niestandardową łatkę. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>&lt;enter&gt;</kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
| `` a `` | Przełącz wszystkie pliki | Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: Toggle file tree view
| `` <enter> `` | Wejdź do pliku / Przełącz zwiń katalog | Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog. | <kbd>/</kbd>: Search the current view by text
| `` ` `` | Przełącz widok drzewa plików | Przełącz widok plików między płaskim a drzewem. Płaski układ pokazuje wszystkie ścieżki plików na jednej liście, układ drzewa grupuje pliki według katalogów. | </pre>
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Podsumowanie commita ## Poczekalnia
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Poprzedni kawałek
| `` <enter> `` | Potwierdź | | <kbd>&lt;right&gt;</kbd>: Następny kawałek
| `` <esc> `` | Zamknij | | <kbd>v</kbd>: Toggle drag select
<kbd>V</kbd>: Toggle drag select
<kbd>a</kbd>: Toggle select hunk
<kbd>&lt;c-o&gt;</kbd>: Copy the selected text to the clipboard
<kbd>o</kbd>: Otwórz plik
<kbd>e</kbd>: Edytuj plik
<kbd>&lt;esc&gt;</kbd>: Wróć do panelu plików
<kbd>&lt;tab&gt;</kbd>: Switch to other panel (staged/unstaged changes)
<kbd>&lt;space&gt;</kbd>: Toggle line staged / unstaged
<kbd>d</kbd>: Discard change (git reset)
<kbd>E</kbd>: Edit hunk
<kbd>c</kbd>: Zatwierdź zmiany
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
<kbd>/</kbd>: Search the current view by text
</pre>
## Reflog ## Reflog
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Kopiuj hash commita do schowka | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Otwórz commit w przeglądarce | | <kbd>n</kbd>: Create new branch off of commit
| `` n `` | Utwórz nową gałąź z commita | | <kbd>g</kbd>: Wyświetl opcje resetu
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | <kbd>c</kbd>: Kopiuj commit (przebieranie)
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | <kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
| `` <c-r> `` | Resetuj wybrane (cherry-picked) commity | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | <kbd>&lt;enter&gt;</kbd>: View commits
| `` <enter> `` | Pokaż commity | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | Zobacz opcje drzewa pracy | | </pre>
| `` / `` | Filtruj bieżący widok po tekście | |
## Remote branches
<pre>
<kbd>&lt;c-o&gt;</kbd>: Copy branch name to clipboard
<kbd>&lt;space&gt;</kbd>: Przełącz
<kbd>n</kbd>: Nowa gałąź
<kbd>M</kbd>: Scal do obecnej gałęzi
<kbd>r</kbd>: Zmiana bazy gałęzi
<kbd>d</kbd>: Usuń gałąź
<kbd>u</kbd>: Set as upstream of checked-out branch
<kbd>g</kbd>: Wyświetl opcje resetu
<kbd>&lt;enter&gt;</kbd>: View commits
<kbd>/</kbd>: Filter the current view by text
</pre>
## Remotes
<pre>
<kbd>f</kbd>: Fetch remote
<kbd>n</kbd>: Add new remote
<kbd>d</kbd>: Remove remote
<kbd>e</kbd>: Edit remote
<kbd>/</kbd>: Filter the current view by text
</pre>
## Scalanie
<pre>
<kbd>e</kbd>: Edytuj plik
<kbd>o</kbd>: Otwórz plik
<kbd>&lt;left&gt;</kbd>: Poprzedni konflikt
<kbd>&lt;right&gt;</kbd>: Następny konflikt
<kbd>&lt;up&gt;</kbd>: Wybierz poprzedni kawałek
<kbd>&lt;down&gt;</kbd>: Wybierz następny kawałek
<kbd>z</kbd>: Cofnij
<kbd>M</kbd>: Open external merge tool (git mergetool)
<kbd>&lt;space&gt;</kbd>: Wybierz kawałek
<kbd>b</kbd>: Wybierz oba kawałki
<kbd>&lt;esc&gt;</kbd>: Wróć do panelu plików
</pre>
## Schowek ## Schowek
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Zastosuj
| `` <space> `` | Zastosuj | Zastosuj wpis schowka do katalogu roboczego. | <kbd>g</kbd>: Wyciągnij
| `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. | <kbd>d</kbd>: Porzuć
| `` d `` | Usuń | Usuń wpis schowka z listy schowka. | <kbd>n</kbd>: Nowa gałąź
| `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. | <kbd>r</kbd>: Rename stash
| `` r `` | Zmień nazwę schowka | | <kbd>&lt;enter&gt;</kbd>: Przeglądaj pliki commita
| `` <enter> `` | Wyświetl pliki | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | Zobacz opcje drzewa pracy | | </pre>
| `` / `` | Filtruj bieżący widok po tekście | |
## Status ## Status
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: Otwórz konfigurację
| `` o `` | Otwórz plik konfiguracyjny | Otwórz plik w domyślnej aplikacji. | <kbd>e</kbd>: Edytuj konfigurację
| `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. | <kbd>u</kbd>: Sprawdź aktualizacje
| `` u `` | Sprawdź aktualizacje | | <kbd>&lt;enter&gt;</kbd>: Switch to a recent repo
| `` <enter> `` | Przełącz na ostatnie repozytorium | | <kbd>a</kbd>: Pokaż wszystkie logi gałęzi
| `` a `` | Pokaż wszystkie gałęzie w logach | | </pre>
## Sub-commity ## Sub-commits
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy commit SHA to clipboard
| `` <c-o> `` | Kopiuj hash commita do schowka | | <kbd>&lt;space&gt;</kbd>: Checkout commit
| `` <space> `` | Przełącz | Przełącz wybrany commit jako odłączoną HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Kopiuj atrybut commita do schowka | Kopiuj atrybut commita do schowka (np. hash, URL, różnice, wiadomość, autor). | <kbd>o</kbd>: Open commit in browser
| `` o `` | Otwórz commit w przeglądarce | | <kbd>n</kbd>: Create new branch off of commit
| `` n `` | Utwórz nową gałąź z commita | | <kbd>g</kbd>: Wyświetl opcje resetu
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | <kbd>c</kbd>: Kopiuj commit (przebieranie)
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. | <kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
| `` <c-r> `` | Resetuj wybrane (cherry-picked) commity | | <kbd>&lt;c-r&gt;</kbd>: Reset cherry-picked (copied) commits selection
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | | <kbd>&lt;enter&gt;</kbd>: Przeglądaj pliki commita
| `` <enter> `` | Wyświetl pliki | | <kbd>/</kbd>: Search the current view by text
| `` w `` | Zobacz opcje drzewa pracy | | </pre>
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Submoduły ## Submodules
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Copy submodule name to clipboard
| `` <c-o> `` | Kopiuj nazwę submodułu do schowka | | <kbd>&lt;enter&gt;</kbd>: Enter submodule
| `` <enter> `` | Wejdź | Wejdź do submodułu. Po wejściu do submodułu możesz nacisnąć `<esc>`, aby wrócić do repozytorium nadrzędnego. | <kbd>d</kbd>: Remove submodule
| `` d `` | Usuń | Usuń wybrany submoduł i odpowiadający mu katalog. | <kbd>u</kbd>: Update submodule
| `` u `` | Aktualizuj | Aktualizuj wybrany submoduł. | <kbd>n</kbd>: Add new submodule
| `` n `` | Nowy submoduł | | <kbd>e</kbd>: Update submodule URL
| `` e `` | Zaktualizuj URL submodułu | | <kbd>i</kbd>: Initialize submodule
| `` i `` | Zainicjuj | Zainicjuj wybrany submoduł, aby przygotować do pobrania. Prawdopodobnie chcesz to kontynuować, wywołując akcję 'update', aby pobrać submoduł. | <kbd>b</kbd>: View bulk submodule options
| `` b `` | Pokaż opcje masowych operacji na submodułach | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filtruj bieżący widok po tekście | | </pre>
## Tagi ## Tags
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Przełącz
| `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). | <kbd>d</kbd>: Delete tag
| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. | <kbd>P</kbd>: Push tag
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. | <kbd>n</kbd>: Create tag
| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. | <kbd>g</kbd>: Wyświetl opcje resetu
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. | <kbd>&lt;enter&gt;</kbd>: View commits
| `` <enter> `` | Pokaż commity | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | Zobacz opcje drzewa pracy | | </pre>
| `` / `` | Filtruj bieżący widok po tekście | |
## Zdalne ## Zwykłe
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: Przewiń w dół (fn+up)
| `` <enter> `` | Wyświetl gałęzie | | <kbd>mouse wheel up</kbd>: Przewiń w górę (fn+down)
| `` n `` | Nowy zdalny | | </pre>
| `` d `` | Usuń | Usuń wybrany zdalny. Wszelkie lokalne gałęzie śledzące gałąź zdalną z tego zdalnego nie zostaną dotknięte. |
| `` e `` | Edytuj | Edytuj nazwę lub URL wybranego zdalnego. |
| `` f `` | Pobierz | Pobierz aktualizacje z zdalnego repozytorium. Pobiera nowe commity i gałęzie bez scalania ich z lokalnymi gałęziami. |
| `` / `` | Filtruj bieżący widok po tekście | |
## Zdalne gałęzie
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | Kopiuj nazwę gałęzi do schowka | |
| `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. |
| `` n `` | Nowa gałąź | |
| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. |
| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. |
| `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. |
| `` u `` | Ustaw jako upstream | Ustaw wybraną gałąź zdalną jako upstream sprawdzonej gałęzi. |
| `` s `` | Kolejność sortowania | |
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
| `` <enter> `` | Pokaż commity | |
| `` w `` | Zobacz opcje drzewa pracy | |
| `` / `` | Filtruj bieżący widok po tekście | |
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit Связки клавиш # Lazygit Связки клавиш
@@ -6,359 +6,328 @@ _Связки клавиш_
## Глобальные сочетания клавиш ## Глобальные сочетания клавиш
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: Переключиться на последний репозиторий
| `` <c-r> `` | Переключиться на последний репозиторий | | <kbd>&lt;pgup&gt;</kbd>: Прокрутить вверх главную панель (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | Прокрутить вверх главную панель | | <kbd>&lt;pgdown&gt;</kbd>: Прокрутить вниз главную панель (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | Прокрутить вниз главную панель | | <kbd>@</kbd>: Открыть меню журнала команд
| `` @ `` | Открыть меню журнала команд | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
| `` P `` | Отправить изменения | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
| `` p `` | Получить и слить изменения | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>:</kbd>: Выполнить пользовательскую команду
| `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: Просмотреть пользовательские параметры патча
| `` { `` | Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: Просмотреть параметры слияния/перебазирования
| `` : `` | Выполнить пользовательскую команду | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: Обновить
| `` <c-p> `` | Просмотреть пользовательские параметры патча | | <kbd>+</kbd>: Следующий режим экрана (нормальный/полуэкранный/полноэкранный)
| `` m `` | Просмотреть параметры слияния/перебазирования | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: Предыдущий режим экрана
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: Открыть меню
| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | | <kbd>&lt;c-s&gt;</kbd>: Просмотреть параметры фильтрации по пути
| `` _ `` | Предыдущий режим экрана | | <kbd>W</kbd>: Открыть меню сравнении
| `` ? `` | Открыть меню | | <kbd>&lt;c-e&gt;</kbd>: Открыть меню сравнении
| `` <c-s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: Переключить отображение изменении пробелов в просмотрщике сравнении
| `` W `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: Отменить (через reflog) (экспериментальный)
| `` <c-e> `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: Повторить (через reflog) (экспериментальный)
| `` q `` | Выйти | | <kbd>P</kbd>: Отправить изменения
| `` <esc> `` | Отменить | | <kbd>p</kbd>: Получить и слить изменения
| `` <c-w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
| `` <c-z> `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
## Навигация по панели списка ## Навигация по панели списка
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: Предыдущая страница
| `` , `` | Предыдущая страница | | <kbd>.</kbd>: Следующая страница
| `` . `` | Следующая страница | | <kbd>&lt;</kbd>: Пролистать наверх
| `` < `` | Пролистать наверх | | <kbd>&gt;</kbd>: Прокрутить вниз
| `` > `` | Прокрутить вниз | | <kbd>/</kbd>: Найти
| `` v `` | Переключить выборку перетаскивания | | <kbd>H</kbd>: Прокрутить влево
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: Прокрутить вправо
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: Следующая вкладка
| `` / `` | Найти | | <kbd>[</kbd>: Предыдущая вкладка
| `` H `` | Прокрутить влево | | </pre>
| `` L `` | Прокрутить вправо | |
| `` ] `` | Следующая вкладка | |
| `` [ `` | Предыдущая вкладка | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
## Главная панель (Индексирование) ## Главная панель (Индексирование)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть
| `` <left> `` | Выбрать предыдущую часть | | <kbd>&lt;right&gt;</kbd>: Выбрать следующую часть
| `` <right> `` | Выбрать следующую часть | | <kbd>v</kbd>: Переключить выборку перетаскивания
| `` v `` | Переключить выборку перетаскивания | | <kbd>V</kbd>: Переключить выборку перетаскивания
| `` a `` | Переключить выборку частей | Toggle hunk selection mode. | <kbd>a</kbd>: Переключить выборку частей
| `` <c-o> `` | Скопировать выделенный текст в буфер обмена | | <kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена
| `` <space> `` | Переключить индекс | Переключить строку в проиндексированные / непроиндексированные | <kbd>o</kbd>: Открыть файл
| `` d `` | Отменить изменение (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: Редактировать файл
| `` o `` | Открыть файл | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: Вернуться к панели файлов
| `` e `` | Редактировать файл | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: Переключиться на другую панель (проиндексированные/непроиндексированные изменения)
| `` <esc> `` | Вернуться к панели файлов | | <kbd>&lt;space&gt;</kbd>: Переключить строку в проиндексированные / непроиндексированные
| `` <tab> `` | Переключиться на другую панель (проиндексированные/непроиндексированные изменения) | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: Отменить изменение (git reset)
| `` E `` | Изменить эту часть | Edit selected hunk in external editor. | <kbd>E</kbd>: Изменить эту часть
| `` c `` | Сохранить изменения | Commit staged changes. | <kbd>c</kbd>: Сохранить изменения
| `` w `` | Закоммитить изменения без предварительного хука коммита | | <kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
| `` C `` | Сохранить изменения с помощью редактора git | | <kbd>C</kbd>: Сохранить изменения с помощью редактора git
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: Найти
| `` / `` | Найти | | </pre>
## Главная панель (Обычный) ## Главная панель (Обычный)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: Прокрутить вниз (fn+up)
| `` mouse wheel down (fn+up) `` | Прокрутить вниз | | <kbd>mouse wheel up</kbd>: Прокрутить вверх (fn+down)
| `` mouse wheel up (fn+down) `` | Прокрутить вверх | | </pre>
## Главная панель (Слияние) ## Главная панель (Слияние)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: Редактировать файл
| `` <space> `` | Выбрать эту часть | | <kbd>o</kbd>: Открыть файл
| `` b `` | Выбрать все части | | <kbd>&lt;left&gt;</kbd>: Выбрать предыдущий конфликт
| `` <up> `` | Выбрать предыдущую часть | | <kbd>&lt;right&gt;</kbd>: Выбрать следующий конфликт
| `` <down> `` | Выбрать следующую часть | | <kbd>&lt;up&gt;</kbd>: Выбрать предыдущую часть
| `` <left> `` | Выбрать предыдущий конфликт | | <kbd>&lt;down&gt;</kbd>: Выбрать следующую часть
| `` <right> `` | Выбрать следующий конфликт | | <kbd>z</kbd>: Отменить
| `` z `` | Отменить | Undo last merge conflict resolution. | <kbd>M</kbd>: Открыть внешний инструмент слияния (git mergetool)
| `` e `` | Редактировать файл | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Выбрать эту часть
| `` o `` | Открыть файл | Open file in default application. | <kbd>b</kbd>: Выбрать все части
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: Вернуться к панели файлов
| `` <esc> `` | Вернуться к панели файлов | | </pre>
## Главная панель (сборка патчей) ## Главная панель (сборка патчей)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: Выбрать предыдущую часть
| `` <left> `` | Выбрать предыдущую часть | | <kbd>&lt;right&gt;</kbd>: Выбрать следующую часть
| `` <right> `` | Выбрать следующую часть | | <kbd>v</kbd>: Переключить выборку перетаскивания
| `` v `` | Переключить выборку перетаскивания | | <kbd>V</kbd>: Переключить выборку перетаскивания
| `` a `` | Переключить выборку частей | Toggle hunk selection mode. | <kbd>a</kbd>: Переключить выборку частей
| `` <c-o> `` | Скопировать выделенный текст в буфер обмена | | <kbd>&lt;c-o&gt;</kbd>: Скопировать выделенный текст в буфер обмена
| `` o `` | Открыть файл | Open file in default application. | <kbd>o</kbd>: Открыть файл
| `` e `` | Редактировать файл | Open file in external editor. | <kbd>e</kbd>: Редактировать файл
| `` <space> `` | Добавить/удалить строку(и) для патча | | <kbd>&lt;space&gt;</kbd>: Добавить/удалить строку(и) для патча
| `` <esc> `` | Выйти из сборщика пользовательских патчей | | <kbd>&lt;esc&gt;</kbd>: Выйти из сборщика пользовательских патчей
| `` / `` | Найти | | <kbd>/</kbd>: Найти
</pre>
## Журнал ссылок (Reflog) ## Журнал ссылок (Reflog)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать SHA коммита в буфер обмена
| `` <c-o> `` | Скопировать hash коммита в буфер обмена | | <kbd>&lt;space&gt;</kbd>: Переключить коммит
| `` <space> `` | Переключить | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Скопировать атрибут коммита
| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Открыть коммит в браузере
| `` o `` | Открыть коммит в браузере | | <kbd>n</kbd>: Создать новую ветку с этого коммита
| `` n `` | Создать новую ветку с этого коммита | | <kbd>g</kbd>: Просмотреть параметры сброса
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | <kbd>&lt;c-r&gt;</kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: Просмотреть коммиты
| `` <enter> `` | Просмотреть коммиты | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Коммиты ## Коммиты
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать SHA коммита в буфер обмена
| `` <c-o> `` | Скопировать hash коммита в буфер обмена | | <kbd>&lt;c-r&gt;</kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | <kbd>b</kbd>: Просмотреть параметры бинарного поиска
| `` b `` | Просмотреть параметры бинарного поиска | | <kbd>s</kbd>: Объединить несколько коммитов в один нижний
| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: Объединить несколько коммитов в один отбросив сообщение коммита
| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: Перефразировать коммит
| `` r `` | Перефразировать коммит | Reword the selected commit's message. | <kbd>R</kbd>: Переписать коммит с помощью редактора
| `` R `` | Переписать коммит с помощью редактора | | <kbd>d</kbd>: Удалить коммит
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: Изменить коммит
| `` e `` | Edit (start interactive rebase) | Изменить коммит | <kbd>p</kbd>: Выбрать коммит (в середине перебазирования)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: Создать fixup коммит для этого коммита
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)
| `` p `` | Pick | Выбрать коммит (в середине перебазирования) | <kbd>&lt;c-j&gt;</kbd>: Переместить коммит вниз на один
| `` F `` | Создать fixup коммит | Создать fixup коммит для этого коммита | <kbd>&lt;c-k&gt;</kbd>: Переместить коммит вверх на один
| `` S `` | Apply fixup commits | Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение) | <kbd>v</kbd>: Вставить отобранные коммиты (cherry-pick)
| `` <c-j> `` | Переместить коммит вниз на один | | <kbd>A</kbd>: Править последний коммит с проиндексированными изменениями
| `` <c-k> `` | Переместить коммит вверх на один | | <kbd>a</kbd>: Установить/убрать автора коммита
| `` V `` | Вставить отобранные коммиты (cherry-pick) | | <kbd>t</kbd>: Отменить коммит
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: Пометить коммит тегом
| `` A `` | Amend | Править последний коммит с проиндексированными изменениями | <kbd>&lt;c-l&gt;</kbd>: Открыть меню журнала
| `` a `` | Установить/убрать автора коммита | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: Переключить коммит
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: Скопировать атрибут коммита
| `` T `` | Пометить коммит тегом | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: Открыть коммит в браузере
| `` <c-l> `` | Открыть меню журнала | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: Создать новую ветку с этого коммита
| `` <space> `` | Переключить | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: Просмотреть параметры сброса
| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
| `` o `` | Открыть коммит в браузере | | <kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
| `` n `` | Создать новую ветку с этого коммита | | <kbd>&lt;enter&gt;</kbd>: Просмотреть файлы выбранного элемента
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Найти
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | Просмотреть файлы выбранного элемента | |
| `` w `` | View worktree options | |
| `` / `` | Найти | |
## Локальные Ветки ## Локальные Ветки
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать название ветки в буфер обмена
| `` <c-o> `` | Скопировать название ветки в буфер обмена | | <kbd>i</kbd>: Показать параметры git-flow
| `` i `` | Показать параметры git-flow | | <kbd>&lt;space&gt;</kbd>: Переключить
| `` <space> `` | Переключить | Checkout selected item. | <kbd>n</kbd>: Новая ветка
| `` n `` | Новая ветка | | <kbd>o</kbd>: Создать запрос на принятие изменений
| `` o `` | Создать запрос на принятие изменений | | <kbd>O</kbd>: Создать параметры запроса принятие изменений
| `` O `` | Создать параметры запроса принятие изменений | | <kbd>&lt;c-y&gt;</kbd>: Скопировать URL запроса на принятие изменений в буфер обмена
| `` <c-y> `` | Скопировать URL запроса на принятие изменений в буфер обмена | | <kbd>c</kbd>: Переключить по названию
| `` c `` | Переключить по названию | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: Принудительное переключение
| `` F `` | Принудительное переключение | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: Удалить ветку
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: Слияние с текущей переключённой веткой
| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: Перемотать эту ветку вперёд из её upstream-ветки
| `` f `` | Перемотать эту ветку вперёд из её upstream-ветки | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: Создать тег
| `` T `` | Создать тег | | <kbd>g</kbd>: Просмотреть параметры сброса
| `` s `` | Порядок сортировки | | <kbd>R</kbd>: Переименовать ветку
| `` g `` | Просмотреть параметры сброса | | <kbd>u</kbd>: Установить/убрать upstream-ветку
| `` R `` | Переименовать ветку | | <kbd>&lt;enter&gt;</kbd>: Просмотреть коммиты
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | Просмотреть коммиты | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Меню ## Меню
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Выполнить
| `` <enter> `` | Выполнить | | <kbd>&lt;esc&gt;</kbd>: Закрыть
| `` <esc> `` | Закрыть | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Панель Подтверждения ## Панель Подтверждения
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Подтвердить
| `` <enter> `` | Подтвердить | | <kbd>&lt;esc&gt;</kbd>: Закрыть/отменить
| `` <esc> `` | Закрыть/отменить | | </pre>
## Подкоммиты ## Подкоммиты
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать SHA коммита в буфер обмена
| `` <c-o> `` | Скопировать hash коммита в буфер обмена | | <kbd>&lt;space&gt;</kbd>: Переключить коммит
| `` <space> `` | Переключить | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Скопировать атрибут коммита
| `` y `` | Скопировать атрибут коммита | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: Открыть коммит в браузере
| `` o `` | Открыть коммит в браузере | | <kbd>n</kbd>: Создать новую ветку с этого коммита
| `` n `` | Создать новую ветку с этого коммита | | <kbd>g</kbd>: Просмотреть параметры сброса
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | | <kbd>&lt;c-r&gt;</kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: Просмотреть файлы выбранного элемента
| `` <enter> `` | Просмотреть файлы выбранного элемента | | <kbd>/</kbd>: Найти
| `` w `` | View worktree options | | </pre>
| `` / `` | Найти | |
## Подмодули ## Подмодули
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать название подмодуля в буфер обмена
| `` <c-o> `` | Скопировать название подмодуля в буфер обмена | | <kbd>&lt;enter&gt;</kbd>: Ввести подмодуль
| `` <enter> `` | Enter | Ввести подмодуль | <kbd>d</kbd>: Удалить подмодуль
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: Обновить подмодуль
| `` u `` | Update | Обновить подмодуль | <kbd>n</kbd>: Добавить новый подмодуль
| `` n `` | Добавить новый подмодуль | | <kbd>e</kbd>: Обновить URL подмодуля
| `` e `` | Обновить URL подмодуля | | <kbd>i</kbd>: Инициализировать подмодуль
| `` i `` | Initialize | Инициализировать подмодуль | <kbd>b</kbd>: Просмотреть параметры массового подмодуля
| `` b `` | Просмотреть параметры массового подмодуля | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## Сводка коммита ## Сводка коммита
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: Подтвердить
| `` <enter> `` | Подтвердить | | <kbd>&lt;esc&gt;</kbd>: Закрыть
| `` <esc> `` | Закрыть | | </pre>
## Сохранить Изменения Файлов ## Сохранить Изменения Файлов
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать закомиченное имя файла в буфер обмена
| `` <c-o> `` | Скопировать название файла в буфер обмена | | <kbd>c</kbd>: Переключить файл
| `` c `` | Переключить | Переключить файл | <kbd>d</kbd>: Отменить изменения коммита в этом файле
| `` d `` | Remove | Отменить изменения коммита в этом файле | <kbd>o</kbd>: Открыть файл
| `` o `` | Открыть файл | Open file in default application. | <kbd>e</kbd>: Редактировать файл
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: Переключить файлы включённые в патч
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Переключить все файлы, включённые в патч
| `` <space> `` | Переключить файлы включённые в патч | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>&lt;enter&gt;</kbd>: Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения)
| `` a `` | Переключить все файлы, включённые в патч | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: Переключить вид дерева файлов
| `` <enter> `` | Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | <kbd>/</kbd>: Найти
| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | Найти | |
## Статус ## Статус
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: Открыть файл конфигурации
| `` o `` | Открыть файл конфигурации | Open file in default application. | <kbd>e</kbd>: Редактировать файл конфигурации
| `` e `` | Редактировать файл конфигурации | Open file in external editor. | <kbd>u</kbd>: Проверить обновления
| `` u `` | Проверить обновления | | <kbd>&lt;enter&gt;</kbd>: Переключиться на последний репозиторий
| `` <enter> `` | Переключиться на последний репозиторий | | <kbd>a</kbd>: Показать все логи ветки
| `` a `` | Показать все логи ветки | | </pre>
## Теги ## Теги
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Переключить
| `` <space> `` | Переключить | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: Удалить тег
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: Отправить тег
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: Создать тег
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: Просмотреть параметры сброса
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: Просмотреть коммиты
| `` <enter> `` | Просмотреть коммиты | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Удалённые ветки ## Удалённые ветки
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать название ветки в буфер обмена
| `` <c-o> `` | Скопировать название ветки в буфер обмена | | <kbd>&lt;space&gt;</kbd>: Переключить
| `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: Новая ветка
| `` n `` | Новая ветка | | <kbd>M</kbd>: Слияние с текущей переключённой веткой
| `` M `` | Слияние с текущей переключённой веткой | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
| `` r `` | Перебазировать переключённую ветку на эту ветку | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: Удалить ветку
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: Установить как upstream-ветку переключённую ветку
| `` u `` | Set as upstream | Установить как upstream-ветку переключённую ветку | <kbd>g</kbd>: Просмотреть параметры сброса
| `` s `` | Порядок сортировки | | <kbd>&lt;enter&gt;</kbd>: Просмотреть коммиты
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | Просмотреть коммиты | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Удалённые репозитории ## Удалённые репозитории
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: Получение изменения из удалённого репозитория
| `` <enter> `` | View branches | | <kbd>n</kbd>: Добавить новую удалённую ветку
| `` n `` | Добавить новую удалённую ветку | | <kbd>d</kbd>: Удалить удалённую ветку
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: Редактировать удалённый репозитории
| `` e `` | Edit | Редактировать удалённый репозитории | <kbd>/</kbd>: Filter the current view by text
| `` f `` | Получить изменения | Получение изменения из удалённого репозитория | </pre>
| `` / `` | Filter the current view by text | |
## Файлы ## Файлы
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: Скопировать название файла в буфер обмена
| `` <c-o> `` | Скопировать название файла в буфер обмена | | <kbd>d</kbd>: Просмотреть параметры «отмены изменении»
| `` <space> `` | Переключить индекс | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: Переключить индекс
| `` <c-b> `` | Фильтровать файлы (проиндексированные/непроиндексированные) | | <kbd>&lt;c-b&gt;</kbd>: Фильтровать файлы (проиндексированные/непроиндексированные)
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: Сохранить изменения
| `` c `` | Сохранить изменения | Commit staged changes. | <kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
| `` w `` | Закоммитить изменения без предварительного хука коммита | | <kbd>A</kbd>: Правка последнего коммита
| `` A `` | Правка последнего коммита | | <kbd>C</kbd>: Сохранить изменения с помощью редактора git
| `` C `` | Сохранить изменения с помощью редактора git | | <kbd>e</kbd>: Редактировать файл
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: Открыть файл
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: Игнорировать или исключить файл
| `` o `` | Открыть файл | Open file in default application. | <kbd>r</kbd>: Обновить файлы
| `` i `` | Игнорировать или исключить файл | | <kbd>s</kbd>: Припрятать все изменения
| `` r `` | Обновить файлы | | <kbd>S</kbd>: Просмотреть параметры хранилища
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: Все проиндексированные/непроиндексированные
| `` S `` | Просмотреть параметры хранилища | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: Проиндексировать отдельные части/строки для файла или свернуть/развернуть для каталога
| `` a `` | Все проиндексированные/непроиндексированные | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: Просмотреть параметры сброса upstream-ветки
| `` <enter> `` | Проиндексировать отдельные части/строки для файла или свернуть/развернуть для каталога | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: Просмотреть параметры сброса
| `` d `` | Просмотреть параметры «отмены изменении» | View options for discarding changes to the selected file. | <kbd>`</kbd>: Переключить вид дерева файлов
| `` g `` | Просмотреть параметры сброса upstream-ветки | | <kbd>M</kbd>: Открыть внешний инструмент слияния (git mergetool)
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: Получить изменения
| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: Найти
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. |
| `` f `` | Получить изменения | Fetch changes from remote. |
| `` / `` | Найти | |
## Хранилище ## Хранилище
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: Применить припрятанные изменения
| `` <space> `` | Применить припрятанные изменения | Apply the stash entry to your working directory. | <kbd>g</kbd>: Применить припрятанные изменения и тут же удалить их из хранилища
| `` g `` | Применить припрятанные изменения и тут же удалить их из хранилища | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: Удалить припрятанные изменения из хранилища
| `` d `` | Удалить припрятанные изменения из хранилища | Remove the stash entry from the stash list. | <kbd>n</kbd>: Новая ветка
| `` n `` | Новая ветка | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Переименовать хранилище
| `` r `` | Переименовать хранилище | | <kbd>&lt;enter&gt;</kbd>: Просмотреть файлы выбранного элемента
| `` <enter> `` | Просмотреть файлы выбранного элемента | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
+266 -297
View File
@@ -1,4 +1,4 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 按键绑定 # Lazygit 按键绑定
@@ -6,359 +6,328 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 全局键绑定 ## 全局键绑定
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: 切换到最近的仓库
| `` <c-r> `` | 切换到最近的仓库 | | <kbd>&lt;pgup&gt;</kbd>: 向上滚动主面板 (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | | <kbd>&lt;pgdown&gt;</kbd>: 向下滚动主面板 (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | | <kbd>@</kbd>: 打开命令日志菜单
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: 扩大差异视图中显示的上下文范围
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: 缩小差异视图中显示的上下文范围
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>:</kbd>: 执行自定义命令
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: 查看自定义补丁选项
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: 查看 合并/变基 选项
| `` : `` | 执行自定义命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: 刷新
| `` <c-p> `` | 查看自定义补丁选项 | | <kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: 上一屏模式
| `` R `` | 刷新 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: 打开菜单
| `` + `` | 下一屏模式(正常/半屏/全屏) | | <kbd>&lt;c-s&gt;</kbd>: 查看按路径过滤选项
| `` _ `` | 上一屏模式 | | <kbd>W</kbd>: 打开 diff 菜单
| `` ? `` | 打开菜单 | | <kbd>&lt;c-e&gt;</kbd>: 打开 diff 菜单
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: 切换是否在差异视图中显示空白字符差异
| `` W `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: (通过 reflog)撤销「实验功能」
| `` <c-e> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: (通过 reflog)重做「实验功能」
| `` q `` | 退出 | | <kbd>P</kbd>: 推送
| `` <esc> `` | 取消 | | <kbd>p</kbd>: 拉取
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | (通过 reflog)撤销「实验功能」 | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## 列表面板导航 ## 列表面板导航
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: 上一页
| `` , `` | 上一页 | | <kbd>.</kbd>: 下一页
| `` . `` | 下一页 | | <kbd>&lt;</kbd>: 滚动到顶部
| `` < `` | 滚动到顶部 | | <kbd>&gt;</kbd>: 滚动到底部
| `` > `` | 滚动到底部 | | <kbd>/</kbd>: 开始搜索
| `` v `` | 切换拖动选择 | | <kbd>H</kbd>: 向左滚动
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: 向右滚动
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: 下一个标签
| `` / `` | 开始搜索 | | <kbd>[</kbd>: 上一个标签
| `` H `` | 向左滚动 | | </pre>
| `` L `` | 向右滚动 | |
| `` ] `` | 下一个标签 | |
| `` [ `` | 上一个标签 | |
## Reflog 页面 ## Reflog 页面
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | | <kbd>&lt;space&gt;</kbd>: 检出提交
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: 在浏览器中打开提交
| `` o `` | 在浏览器中打开提交 | | <kbd>n</kbd>: 从提交创建新分支
| `` n `` | 从提交创建新分支 | | <kbd>g</kbd>: 查看重置选项
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: 复制提交(拣选)
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: 复制提交范围(拣选)
| `` <c-r> `` | 重置已拣选(复制)的提交 | | <kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: 查看提交
| `` <enter> `` | 查看提交 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
## 分支页面 ## 分支页面
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将分支名称复制到剪贴板
| `` <c-o> `` | 将分支名称复制到剪贴板 | | <kbd>i</kbd>: 显示 git-flow 选项
| `` i `` | 显示 git-flow 选项 | | <kbd>&lt;space&gt;</kbd>: 检出
| `` <space> `` | 检出 | Checkout selected item. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | | <kbd>o</kbd>: 创建抓取请求
| `` o `` | 创建抓取请求 | | <kbd>O</kbd>: 创建抓取请求选项
| `` O `` | 创建抓取请求选项 | | <kbd>&lt;c-y&gt;</kbd>: 将抓取请求 URL 复制到剪贴板
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | | <kbd>c</kbd>: 按名称检出
| `` c `` | 按名称检出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: 强制检出
| `` F `` | 强制检出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: 删除分支
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: 将已检出的分支变基到该分支
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: 合并到当前检出的分支
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: 从上游快进此分支
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: 创建标签
| `` T `` | 创建标签 | | <kbd>g</kbd>: 查看重置选项
| `` s `` | Sort order | | <kbd>R</kbd>: 重命名分支
| `` g `` | 查看重置选项 | | <kbd>u</kbd>: Set/Unset upstream
| `` R `` | 重命名分支 | | <kbd>&lt;enter&gt;</kbd>: 查看提交
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 查看提交 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 子提交 ## 子提交
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | | <kbd>&lt;space&gt;</kbd>: 检出提交
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: Copy commit attribute
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: 在浏览器中打开提交
| `` o `` | 在浏览器中打开提交 | | <kbd>n</kbd>: 从提交创建新分支
| `` n `` | 从提交创建新分支 | | <kbd>g</kbd>: 查看重置选项
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: 复制提交(拣选)
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: 复制提交范围(拣选)
| `` <c-r> `` | 重置已拣选(复制)的提交 | | <kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: 查看提交的文件
| `` <enter> `` | 查看提交的文件 | | <kbd>/</kbd>: 开始搜索
| `` w `` | View worktree options | | </pre>
| `` / `` | 开始搜索 | |
## 子模块 ## 子模块
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将子模块名称复制到剪贴板
| `` <c-o> `` | 将子模块名称复制到剪贴板 | | <kbd>&lt;enter&gt;</kbd>: 输入子模块
| `` <enter> `` | Enter | 输入子模块 | <kbd>d</kbd>: 删除子模块
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: 更新子模块
| `` u `` | Update | 更新子模块 | <kbd>n</kbd>: 添加新的子模块
| `` n `` | 添加新的子模块 | | <kbd>e</kbd>: 更新子模块 URL
| `` e `` | 更新子模块 URL | | <kbd>i</kbd>: 初始化子模块
| `` i `` | Initialize | 初始化子模块 | <kbd>b</kbd>: 查看批量子模块选项
| `` b `` | 查看批量子模块选项 | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## 提交 ## 提交
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | | <kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
| `` <c-r> `` | 重置已拣选(复制)的提交 | | <kbd>b</kbd>: 查看二分查找选项
| `` b `` | 查看二分查找选项 | | <kbd>s</kbd>: 向下压缩
| `` s `` | 压缩 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: 修正提交(fixup)
| `` f `` | 修正(fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: 改写提交
| `` r `` | 改写提交 | Reword the selected commit's message. | <kbd>R</kbd>: 使用编辑器重命名提交
| `` R `` | 使用编辑器重命名提交 | | <kbd>d</kbd>: 删除提交
| `` d `` | 删除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: 编辑提交
| `` e `` | Edit (start interactive rebase) | 编辑提交 | <kbd>p</kbd>: 选择提交(变基过程中)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: 创建修正提交
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
| `` p `` | Pick | 选择提交(变基过程中) | <kbd>&lt;c-j&gt;</kbd>: 下移提交
| `` F `` | 为此提交创建修正 | 创建修正提交 | <kbd>&lt;c-k&gt;</kbd>: 上移提交
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) | <kbd>v</kbd>: 粘贴提交(拣选)
| `` <c-j> `` | 下移提交 | | <kbd>A</kbd>: 用已暂存的更改来修补提交
| `` <c-k> `` | 上移提交 | | <kbd>a</kbd>: Set/Reset commit author
| `` V `` | 粘贴提交(拣选) | | <kbd>t</kbd>: 还原提交
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. | <kbd>T</kbd>: 标签提交
| `` A `` | Amend | 用已暂存的更改来修补提交 | <kbd>&lt;c-l&gt;</kbd>: 打开日志菜单
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: 检出提交
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: Copy commit attribute
| `` T `` | 标签提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: 在浏览器中打开提交
| `` <c-l> `` | 打开日志菜单 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: 从提交创建新分支
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: 查看重置选项
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: 复制提交(拣选)
| `` o `` | 在浏览器中打开提交 | | <kbd>C</kbd>: 复制提交范围(拣选)
| `` n `` | 从提交创建新分支 | | <kbd>&lt;enter&gt;</kbd>: 查看提交的文件
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: 开始搜索
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` / `` | 开始搜索 | |
## 提交文件 ## 提交文件
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将提交的文件名复制到剪贴板
| `` <c-o> `` | 将文件名复制到剪贴板 | | <kbd>c</kbd>: 检出文件
| `` c `` | 检出 | 检出文件 | <kbd>d</kbd>: 放弃对此文件的提交更改
| `` d `` | Remove | 放弃对此文件的提交更改 | <kbd>o</kbd>: 打开文件
| `` o `` | 打开文件 | Open file in default application. | <kbd>e</kbd>: 编辑文件
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: 补丁中包含的切换文件
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: Toggle all files included in patch
| `` <space> `` | 补丁中包含的切换文件 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>&lt;enter&gt;</kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: 切换文件树视图
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | <kbd>/</kbd>: 开始搜索
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | 开始搜索 | |
## 提交讯息 ## 提交讯息
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 确认
| `` <enter> `` | 确认 | | <kbd>&lt;esc&gt;</kbd>: 关闭
| `` <esc> `` | 关闭 | | </pre>
## 文件 ## 文件
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将文件名复制到剪贴板
| `` <c-o> `` | 将文件名复制到剪贴板 | | <kbd>d</kbd>: 查看'放弃更改'选项
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: 切换暂存状态
| `` <c-b> `` | Filter files by status | | <kbd>&lt;c-b&gt;</kbd>: Filter files by status
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: 提交更改
| `` c `` | 提交更改 | Commit staged changes. | <kbd>w</kbd>: 提交更改而无需预先提交钩子
| `` w `` | 提交更改而无需预先提交钩子 | | <kbd>A</kbd>: 修补最后一次提交
| `` A `` | 修补最后一次提交 | | <kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
| `` C `` | 提交更改(使用编辑器编辑提交信息) | | <kbd>e</kbd>: 编辑文件
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: 打开文件
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: 忽略文件
| `` o `` | 打开文件 | Open file in default application. | <kbd>r</kbd>: 刷新文件
| `` i `` | 忽略文件 | | <kbd>s</kbd>: 将所有更改加入贮藏
| `` r `` | 刷新文件 | | <kbd>S</kbd>: 查看贮藏选项
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: 切换所有文件的暂存状态
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: 查看上游重置选项
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: 查看重置选项
| `` d `` | 查看'放弃更改'选项 | View options for discarding changes to the selected file. | <kbd>`</kbd>: 切换文件树视图
| `` g `` | 查看上游重置选项 | | <kbd>M</kbd>: 打开外部合并工具 (git mergetool)
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: 抓取
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: 开始搜索
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 抓取 | Fetch changes from remote. |
| `` / `` | 开始搜索 | |
## 构建补丁中 ## 构建补丁中
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 选择上一个区块
| `` <left> `` | 选择上一个区块 | | <kbd>&lt;right&gt;</kbd>: 选择下一个区块
| `` <right> `` | 选择下一个区块 | | <kbd>v</kbd>: 切换拖动选择
| `` v `` | 切换拖动选择 | | <kbd>V</kbd>: 切换拖动选择
| `` a `` | 切换选择区块 | Toggle hunk selection mode. | <kbd>a</kbd>: 切换选择区块
| `` <c-o> `` | 将选中文本复制到剪贴板 | | <kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
| `` o `` | 打开文件 | Open file in default application. | <kbd>o</kbd>: 打开文件
| `` e `` | 编辑文件 | Open file in external editor. | <kbd>e</kbd>: 编辑文件
| `` <space> `` | 添加/移除 行到补丁 | | <kbd>&lt;space&gt;</kbd>: 添加/移除 行到补丁
| `` <esc> `` | 退出逐行模式 | | <kbd>&lt;esc&gt;</kbd>: 退出逐行模式
| `` / `` | 开始搜索 | | <kbd>/</kbd>: 开始搜索
</pre>
## 标签页面 ## 标签页面
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 检出
| `` <space> `` | 检出 | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: 删除标签
| `` n `` | 创建标签 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: 推送标签
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: 创建标签
| `` P `` | 推送标签 | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: 查看重置选项
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: 查看提交
| `` <enter> `` | 查看提交 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## 正在合并 ## 正在合并
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>e</kbd>: 编辑文件
| `` <space> `` | 选中区块 | | <kbd>o</kbd>: 打开文件
| `` b `` | 选中所有区块 | | <kbd>&lt;left&gt;</kbd>: 选择上一个冲突
| `` <up> `` | 选择顶部块 | | <kbd>&lt;right&gt;</kbd>: 选择下一个冲突
| `` <down> `` | 选择底部块 | | <kbd>&lt;up&gt;</kbd>: 选择顶部块
| `` <left> `` | 选择上一个冲突 | | <kbd>&lt;down&gt;</kbd>: 选择底部块
| `` <right> `` | 选择下一个冲突 | | <kbd>z</kbd>: 撤销
| `` z `` | 撤销 | Undo last merge conflict resolution. | <kbd>M</kbd>: 打开外部合并工具 (git mergetool)
| `` e `` | 编辑文件 | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: 选中区块
| `` o `` | 打开文件 | Open file in default application. | <kbd>b</kbd>: 选中所有区块
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. | <kbd>&lt;esc&gt;</kbd>: 返回文件面板
| `` <esc> `` | 返回文件面板 | | </pre>
## 正在暂存 ## 正在暂存
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 选择上一个区块
| `` <left> `` | 选择上一个区块 | | <kbd>&lt;right&gt;</kbd>: 选择下一个区块
| `` <right> `` | 选择下一个区块 | | <kbd>v</kbd>: 切换拖动选择
| `` v `` | 切换拖动选择 | | <kbd>V</kbd>: 切换拖动选择
| `` a `` | 切换选择区块 | Toggle hunk selection mode. | <kbd>a</kbd>: 切换选择区块
| `` <c-o> `` | 将选中文本复制到剪贴板 | | <kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 | <kbd>o</kbd>: 打开文件
| `` d `` | 取消变更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. | <kbd>e</kbd>: 编辑文件
| `` o `` | 打开文件 | Open file in default application. | <kbd>&lt;esc&gt;</kbd>: 返回文件面板
| `` e `` | 编辑文件 | Open file in external editor. | <kbd>&lt;tab&gt;</kbd>: 切换到其他面板
| `` <esc> `` | 返回文件面板 | | <kbd>&lt;space&gt;</kbd>: 切换行暂存状态
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). | <kbd>d</kbd>: 取消变更 (git reset)
| `` E `` | Edit hunk | Edit selected hunk in external editor. | <kbd>E</kbd>: Edit hunk
| `` c `` | 提交更改 | Commit staged changes. | <kbd>c</kbd>: 提交更改
| `` w `` | 提交更改而无需预先提交钩子 | | <kbd>w</kbd>: 提交更改而无需预先提交钩子
| `` C `` | 提交更改(使用编辑器编辑提交信息) | | <kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>/</kbd>: 开始搜索
| `` / `` | 开始搜索 | | </pre>
## 正常 ## 正常
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
| `` mouse wheel down (fn+up) `` | 向下滚动 | | <kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
| `` mouse wheel up (fn+down) `` | 向上滚动 | | </pre>
## 状态 ## 状态
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: 打开配置文件
| `` o `` | 打开配置文件 | Open file in default application. | <kbd>e</kbd>: 编辑配置文件
| `` e `` | 编辑配置文件 | Open file in external editor. | <kbd>u</kbd>: 检查更新
| `` u `` | 检查更新 | | <kbd>&lt;enter&gt;</kbd>: 切换到最近的仓库
| `` <enter> `` | 切换到最近的仓库 | | <kbd>a</kbd>: 显示所有分支的日志
| `` a `` | 显示所有分支的日志 | | </pre>
## 确认面板 ## 确认面板
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 确认
| `` <enter> `` | 确认 | | <kbd>&lt;esc&gt;</kbd>: 关闭
| `` <esc> `` | 关闭 | | </pre>
## 菜单 ## 菜单
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 执行
| `` <enter> `` | 执行 | | <kbd>&lt;esc&gt;</kbd>: 关闭
| `` <esc> `` | 关闭 | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | Filter the current view by text | | </pre>
## 贮藏 ## 贮藏
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 应用
| `` <space> `` | 应用 | Apply the stash entry to your working directory. | <kbd>g</kbd>: 应用并删除
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: 删除
| `` d `` | 删除 | Remove the stash entry from the stash list. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: Rename stash
| `` r `` | Rename stash | | <kbd>&lt;enter&gt;</kbd>: 查看提交的文件
| `` <enter> `` | 查看提交的文件 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | View worktree options | | </pre>
| `` / `` | Filter the current view by text | |
## 远程分支 ## 远程分支
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 将分支名称复制到剪贴板
| `` <c-o> `` | 将分支名称复制到剪贴板 | | <kbd>&lt;space&gt;</kbd>: 检出
| `` <space> `` | 检出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | | <kbd>M</kbd>: 合并到当前检出的分支
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: 将已检出的分支变基到该分支
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: 删除分支
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: 设置为检出分支的上游
| `` u `` | Set as upstream | 设置为检出分支的上游 | <kbd>g</kbd>: 查看重置选项
| `` s `` | Sort order | | <kbd>&lt;enter&gt;</kbd>: 查看提交
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 查看提交 | | </pre>
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## 远程页面 ## 远程页面
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: 抓取远程仓库
| `` <enter> `` | View branches | | <kbd>n</kbd>: 添加新的远程仓库
| `` n `` | 添加新的远程仓库 | | <kbd>d</kbd>: 删除远程
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: 编辑远程仓库
| `` e `` | Edit | 编辑远程仓库 | <kbd>/</kbd>: Filter the current view by text
| `` f `` | 抓取 | 抓取远程仓库 | </pre>
| `` / `` | Filter the current view by text | |
+281 -312
View File
@@ -1,364 +1,333 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._ _This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
# Lazygit 鍵盤快捷鍵 # Lazygit 鍵盤快捷鍵
_說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
## 全域快捷鍵 ## 全局快捷鍵
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-r&gt;</kbd>: 切換到最近使用的版本庫
| `` <c-r> `` | 切換到最近使用的版本庫 | | <kbd>&lt;pgup&gt;</kbd>: 向上捲動主面板 (fn+up/shift+k)
| `` <pgup> (fn+up/shift+k) `` | 向上捲動主面板 | | <kbd>&lt;pgdown&gt;</kbd>: 向下捲動主面板 (fn+down/shift+j)
| `` <pgdown> (fn+down/shift+j) `` | 向下捲動主面板 | | <kbd>@</kbd>: 開啟命令記錄選單
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. | <kbd>}</kbd>: 增加差異檢視中顯示變更周圍上下文的大小
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>{</kbd>: 減小差異檢視中顯示變更周圍上下文的大小
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | <kbd>:</kbd>: 執行自訂命令
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view. | <kbd>&lt;c-p&gt;</kbd>: 檢視自訂補丁選項
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view. | <kbd>m</kbd>: 查看合併/變基選項
| `` : `` | 執行自訂命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. | <kbd>R</kbd>: 重新整理
| `` <c-p> `` | 檢視自訂補丁選項 | | <kbd>+</kbd>: 下一個螢幕模式(常規/半螢幕/全螢幕)
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. | <kbd>_</kbd>: 上一個螢幕模式
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. | <kbd>?</kbd>: 開啟選單
| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | | <kbd>&lt;c-s&gt;</kbd>: 檢視篩選路徑選項
| `` _ `` | 上一個螢幕模式 | | <kbd>W</kbd>: 開啟差異比較選單
| `` ? `` | 開啟選單 | | <kbd>&lt;c-e&gt;</kbd>: 開啟差異比較選單
| `` <c-s> `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. | <kbd>&lt;c-w&gt;</kbd>: 切換是否在差異檢視中顯示空格變更
| `` W `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>z</kbd>: 復原
| `` <c-e> `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. | <kbd>&lt;c-z&gt;</kbd>: 取消復原
| `` q `` | 結束 | | <kbd>P</kbd>: 推送
| `` <esc> `` | 取消 | | <kbd>p</kbd>: 拉取
| `` <c-w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view. | </pre>
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
| `` <c-z> `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
## 移動 ## 列表面板導航
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>,</kbd>: 上一頁
| `` , `` | 上一頁 | | <kbd>.</kbd>: 下一頁
| `` . `` | 下一頁 | | <kbd>&lt;</kbd>: 捲動到頂部
| `` < `` | 捲動到頂部 | | <kbd>&gt;</kbd>: 捲動到底部
| `` > `` | 捲動到底部 | | <kbd>/</kbd>: 開始搜尋
| `` v `` | 切換拖曳選擇 | | <kbd>H</kbd>: 向左捲動
| `` <s-down> `` | Range select down | | <kbd>L</kbd>: 向右捲動
| `` <s-up> `` | Range select up | | <kbd>]</kbd>: 下一個索引標籤
| `` / `` | 搜尋 | | <kbd>[</kbd>: 上一個索引標籤
| `` H `` | 向左捲動 | | </pre>
| `` L `` | 向右捲動 | |
| `` ] `` | 下一個索引標籤 | | ## Reflog
| `` [ `` | 上一個索引標籤 | |
<pre>
<kbd>&lt;c-o&gt;</kbd>: 複製提交 SHA 到剪貼簿
<kbd>&lt;space&gt;</kbd>: 檢出提交
<kbd>y</kbd>: 複製提交屬性
<kbd>o</kbd>: 在瀏覽器中開啟提交
<kbd>n</kbd>: 從提交建立新分支
<kbd>g</kbd>: 檢視重設選項
<kbd>c</kbd>: 複製提交 (揀選)
<kbd>C</kbd>: 複製提交範圍 (揀選)
<kbd>&lt;c-r&gt;</kbd>: 重設選定的揀選 (複製) 提交
<kbd>&lt;enter&gt;</kbd>: 檢視提交
<kbd>/</kbd>: Filter the current view by text
</pre>
## 主視窗 (一般)
<pre>
<kbd>mouse wheel down</kbd>: 向下捲動 (fn+up)
<kbd>mouse wheel up</kbd>: 向上捲動 (fn+down)
</pre>
## 主視窗 (合併中)
<pre>
<kbd>e</kbd>: 編輯檔案
<kbd>o</kbd>: 開啟檔案
<kbd>&lt;left&gt;</kbd>: 選擇上一個衝突
<kbd>&lt;right&gt;</kbd>: 選擇下一個衝突
<kbd>&lt;up&gt;</kbd>: 選擇上一段
<kbd>&lt;down&gt;</kbd>: 選擇下一段
<kbd>z</kbd>: 復原
<kbd>M</kbd>: 開啟外部合併工具 (git mergetool)
<kbd>&lt;space&gt;</kbd>: 挑選程式碼片段
<kbd>b</kbd>: 挑選所有程式碼片段
<kbd>&lt;esc&gt;</kbd>: 返回檔案面板
</pre>
## 主視窗 (預存中)
<pre>
<kbd>&lt;left&gt;</kbd>: 選擇上一段
<kbd>&lt;right&gt;</kbd>: 選擇下一段
<kbd>v</kbd>: 切換拖曳選擇
<kbd>V</kbd>: 切換拖曳選擇
<kbd>a</kbd>: 切換選擇程式碼塊
<kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿
<kbd>o</kbd>: 開啟檔案
<kbd>e</kbd>: 編輯檔案
<kbd>&lt;esc&gt;</kbd>: 返回檔案面板
<kbd>&lt;tab&gt;</kbd>: 切換至另一個面板 (已預存/未預存更改)
<kbd>&lt;space&gt;</kbd>: 切換現有行的狀態 (已預存/未預存)
<kbd>d</kbd>: 刪除變更 (git reset)
<kbd>E</kbd>: 編輯程式碼塊
<kbd>c</kbd>: 提交變更
<kbd>w</kbd>: 沒有預提交 hook 就提交更改
<kbd>C</kbd>: 使用 git 編輯器提交變更
<kbd>/</kbd>: 開始搜尋
</pre>
## 主面板 (補丁生成) ## 主面板 (補丁生成)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;left&gt;</kbd>: 選擇上一段
| `` <left> `` | 選擇上一段 | | <kbd>&lt;right&gt;</kbd>: 選擇下一段
| `` <right> `` | 選擇下一段 | | <kbd>v</kbd>: 切換拖曳選擇
| `` v `` | 切換拖曳選擇 | | <kbd>V</kbd>: 切換拖曳選擇
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. | <kbd>a</kbd>: 切換選擇程式碼塊
| `` <c-o> `` | 複製所選文本至剪貼簿 | | <kbd>&lt;c-o&gt;</kbd>: 複製所選文本至剪貼簿
| `` o `` | 開啟檔案 | Open file in default application. | <kbd>o</kbd>: 開啟檔案
| `` e `` | 編輯檔案 | Open file in external editor. | <kbd>e</kbd>: 編輯檔案
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | | <kbd>&lt;space&gt;</kbd>: 向 (或從) 補丁中添加/刪除行
| `` <esc> `` | 退出自訂補丁建立器 | | <kbd>&lt;esc&gt;</kbd>: 退出自訂補丁建立器
| `` / `` | 搜尋 | | <kbd>/</kbd>: 開始搜尋
</pre>
## 主面板(一般)
| Key | Action | Info |
|-----|--------|-------------|
| `` mouse wheel down (fn+up) `` | 向下捲動 | |
| `` mouse wheel up (fn+down) `` | 向上捲動 | |
## 主面板(合併)
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 挑選程式碼片段 | |
| `` b `` | 挑選所有程式碼片段 | |
| `` <up> `` | 選擇上一段 | |
| `` <down> `` | 選擇下一段 | |
| `` <left> `` | 選擇上一個衝突 | |
| `` <right> `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` <esc> `` | 返回檔案面板 | |
## 主面板(預存)
| Key | Action | Info |
|-----|--------|-------------|
| `` <left> `` | 選擇上一段 | |
| `` <right> `` | 選擇下一段 | |
| `` v `` | 切換拖曳選擇 | |
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. |
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. |
| `` c `` | 提交變更 | Commit staged changes. |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` C `` | 使用 git 編輯器提交變更 | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 搜尋 | |
## 功能表 ## 功能表
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 執行
| `` <enter> `` | 執行 | | <kbd>&lt;esc&gt;</kbd>: 關閉
| `` <esc> `` | 關閉 | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | 搜尋 | | </pre>
## 子提交 ## 子提交
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製提交 SHA 到剪貼簿
| `` <c-o> `` | 複製提交 hash 到剪貼簿 | | <kbd>&lt;space&gt;</kbd>: 檢出提交
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | <kbd>y</kbd>: 複製提交屬性
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>o</kbd>: 在瀏覽器中開啟提交
| `` o `` | 在瀏覽器中開啟提交 | | <kbd>n</kbd>: 從提交建立新分支
| `` n `` | 從提交建立新分支 | | <kbd>g</kbd>: 檢視重設選項
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>c</kbd>: 複製提交 (揀選)
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | <kbd>C</kbd>: 複製提交範圍 (揀選)
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | | <kbd>&lt;c-r&gt;</kbd>: 重設選定的揀選 (複製) 提交
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>&lt;enter&gt;</kbd>: 檢視所選項目的檔案
| `` <enter> `` | 檢視所選項目的檔案 | | <kbd>/</kbd>: 開始搜尋
| `` w `` | 檢視工作目錄選項 | | </pre>
| `` / `` | 搜尋 | |
## 子模組 ## 子模組
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製子模組名稱到剪貼簿
| `` <c-o> `` | 複製子模組名稱到剪貼簿 | | <kbd>&lt;enter&gt;</kbd>: 進入子模組
| `` <enter> `` | Enter | 進入子模組 | <kbd>d</kbd>: 移除子模組
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. | <kbd>u</kbd>: 更新子模組
| `` u `` | Update | 更新子模組 | <kbd>n</kbd>: 新增子模組
| `` n `` | 新增子模組 | | <kbd>e</kbd>: 更新子模組 URL
| `` e `` | 更新子模組 URL | | <kbd>i</kbd>: 初始化子模組
| `` i `` | Initialize | 初始化子模組 | <kbd>b</kbd>: 查看批量子模組選項
| `` b `` | 查看批量子模組選項 | | <kbd>/</kbd>: Filter the current view by text
| `` / `` | 搜尋 | | </pre>
## 工作目錄
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | 搜尋 | |
## 提交 ## 提交
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製提交 SHA 到剪貼簿
| `` <c-o> `` | 複製提交 hash 到剪貼簿 | | <kbd>&lt;c-r&gt;</kbd>: 重設選定的揀選 (複製) 提交
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | | <kbd>b</kbd>: 查看二分選項
| `` b `` | 查看二分選項 | | <kbd>s</kbd>: 向下壓縮
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. | <kbd>f</kbd>: 修復提交 (Fixup)
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. | <kbd>r</kbd>: 改寫提交
| `` r `` | 改寫提交 | Reword the selected commit's message. | <kbd>R</kbd>: 使用編輯器改寫提交
| `` R `` | 使用編輯器改寫提交 | | <kbd>d</kbd>: 刪除提交
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. | <kbd>e</kbd>: 編輯提交
| `` e `` | Edit (start interactive rebase) | 編輯提交 | <kbd>p</kbd>: 挑選提交 (於變基過程中)
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit. <kbd>F</kbd>: 為此提交建立修復提交
If you would instead like to start an interactive rebase from the selected commit, press `e`. | <kbd>S</kbd>: 壓縮上方所有的“fixup!”提交 (自動壓縮)
| `` p `` | Pick | 挑選提交 (於變基過程中) | <kbd>&lt;c-j&gt;</kbd>: 向下移動提交
| `` F `` | 建立修復提交 | 為此提交建立修復提交 | <kbd>&lt;c-k&gt;</kbd>: 向上移動提交
| `` S `` | 壓縮上方所有「fixup」提交(自動壓縮) | 是否壓縮上方 {{.commit}} 所有「fixup」提交? | <kbd>v</kbd>: 貼上提交 (揀選)
| `` <c-j> `` | 向下移動提交 | | <kbd>A</kbd>: 使用已預存的更改修正提交
| `` <c-k> `` | 向上移動提交 | | <kbd>a</kbd>: 設置/重設提交作者
| `` V `` | 貼上提交 (揀選) | | <kbd>t</kbd>: 還原提交
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 | <kbd>T</kbd>: 打標籤到提交
| `` A `` | Amend | 使用已預存的更改修正提交 | <kbd>&lt;c-l&gt;</kbd>: 開啟記錄選單
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. | <kbd>&lt;space&gt;</kbd>: 檢出提交
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. | <kbd>y</kbd>: 複製提交屬性
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. | <kbd>o</kbd>: 在瀏覽器中開啟提交
| `` <c-l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. | <kbd>n</kbd>: 從提交建立新分支
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. | <kbd>g</kbd>: 檢視重設選項
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). | <kbd>c</kbd>: 複製提交 (揀選)
| `` o `` | 在瀏覽器中開啟提交 | | <kbd>C</kbd>: 複製提交範圍 (揀選)
| `` n `` | 從提交建立新分支 | | <kbd>&lt;enter&gt;</kbd>: 檢視所選項目的檔案
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: 開始搜尋
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. | </pre>
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
## 提交摘要 ## 提交摘要
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 確認
| `` <enter> `` | 確認 | | <kbd>&lt;esc&gt;</kbd>: 關閉
| `` <esc> `` | 關閉 | | </pre>
## 提交檔案 ## 提交檔案
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製提交的檔案名稱到剪貼簿
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | | <kbd>c</kbd>: 檢出檔案
| `` c `` | 檢出 | 檢出檔案 | <kbd>d</kbd>: 捨棄此提交對此檔案的更改
| `` d `` | Remove | 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. | <kbd>o</kbd>: 開啟檔案
| `` o `` | 開啟檔案 | Open file in default application. | <kbd>e</kbd>: 編輯檔案
| `` e `` | Edit | Open file in external editor. | <kbd>&lt;space&gt;</kbd>: 切換檔案是否包含在補丁中
| `` <c-t> `` | Open external diff tool (git difftool) | | <kbd>a</kbd>: 切換所有檔案是否包含在補丁中
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>&lt;enter&gt;</kbd>: 輸入檔案以將選定的行添加至補丁(或切換目錄折疊)
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. | <kbd>`</kbd>: 切換檔案樹狀視圖
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. | <kbd>/</kbd>: 開始搜尋
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | </pre>
| `` / `` | 搜尋 | |
## 收藏 (Stash) ## 收藏 (Stash)
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 套用
| `` <space> `` | 套用 | Apply the stash entry to your working directory. | <kbd>g</kbd>: 還原
| `` g `` | 還原 | Apply the stash entry to your working directory and remove the stash entry. | <kbd>d</kbd>: 捨棄
| `` d `` | 捨棄 | Remove the stash entry from the stash list. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. | <kbd>r</kbd>: 重新命名收藏
| `` r `` | 重新命名收藏 | | <kbd>&lt;enter&gt;</kbd>: 檢視所選項目的檔案
| `` <enter> `` | 檢視所選項目的檔案 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | 檢視工作目錄選項 | | </pre>
| `` / `` | 搜尋 | |
## 日誌
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 複製提交 hash 到剪貼簿 | |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | 複製提交屬性 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | 在瀏覽器中開啟提交 | |
| `` n `` | 從提交建立新分支 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
## 本地分支 ## 本地分支
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製分支名稱到剪貼簿
| `` <c-o> `` | 複製分支名稱到剪貼簿 | | <kbd>i</kbd>: 顯示 git-flow 選項
| `` i `` | 顯示 git-flow 選項 | | <kbd>&lt;space&gt;</kbd>: 檢出
| `` <space> `` | 檢出 | Checkout selected item. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | | <kbd>o</kbd>: 建立拉取請求
| `` o `` | 建立拉取請求 | | <kbd>O</kbd>: 建立拉取請求選項
| `` O `` | 建立拉取請求選項 | | <kbd>&lt;c-y&gt;</kbd>: 複製拉取請求的 URL 到剪貼板
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | | <kbd>c</kbd>: 根據名稱檢出
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. | <kbd>F</kbd>: 強制檢出
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. | <kbd>d</kbd>: 刪除分支
| `` d `` | Delete | View delete options for local/remote branch. | <kbd>r</kbd>: 將已檢出的分支變基至此分支
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | <kbd>M</kbd>: 合併到當前檢出的分支
| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. | <kbd>f</kbd>: 從上游快進此分支
| `` f `` | 從上游快進此分支 | Fast-forward selected branch from its upstream. | <kbd>T</kbd>: 建立標籤
| `` T `` | 建立標籤 | | <kbd>g</kbd>: 檢視重設選項
| `` s `` | Sort order | | <kbd>R</kbd>: 重新命名分支
| `` g `` | 檢視重設選項 | | <kbd>u</kbd>: 設定/取消設定上游
| `` R `` | 重新命名分支 | | <kbd>&lt;enter&gt;</kbd>: 檢視提交
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游) | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 檢視提交 | | </pre>
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
## 標籤 ## 標籤
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;space&gt;</kbd>: 檢出
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. | <kbd>d</kbd>: 刪除標籤
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. | <kbd>P</kbd>: 推送標籤
| `` d `` | Delete | View delete options for local/remote tag. | <kbd>n</kbd>: 建立標籤
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. | <kbd>g</kbd>: 檢視重設選項
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>&lt;enter&gt;</kbd>: 檢視提交
| `` <enter> `` | 檢視提交 | | <kbd>/</kbd>: Filter the current view by text
| `` w `` | 檢視工作目錄選項 | | </pre>
| `` / `` | 搜尋 | |
## 檔案 ## 檔案
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製檔案名稱到剪貼簿
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | | <kbd>d</kbd>: 檢視“捨棄更改”的選項
| `` <space> `` | 切換預存 | Toggle staged for selected file. | <kbd>&lt;space&gt;</kbd>: 切換預存
| `` <c-b> `` | 篩選檔案 (預存/未預存) | | <kbd>&lt;c-b&gt;</kbd>: 篩選檔案 (預存/未預存)
| `` y `` | Copy to clipboard | | <kbd>c</kbd>: 提交變更
| `` c `` | 提交變更 | Commit staged changes. | <kbd>w</kbd>: 沒有預提交 hook 就提交更改
| `` w `` | 沒有預提交 hook 就提交更改 | | <kbd>A</kbd>: 修正上次提交
| `` A `` | 修改上次提交 | | <kbd>C</kbd>: 使用 git 編輯器提交變更
| `` C `` | 使用 git 編輯器提交變更 | | <kbd>e</kbd>: 編輯檔案
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> | <kbd>o</kbd>: 開啟檔案
| `` e `` | Edit | Open file in external editor. | <kbd>i</kbd>: 忽略或排除檔案
| `` o `` | 開啟檔案 | Open file in default application. | <kbd>r</kbd>: 重新整理檔案
| `` i `` | 忽略或排除檔案 | | <kbd>s</kbd>: 收藏所有變更
| `` r `` | 重新整理檔案 | | <kbd>S</kbd>: 檢視收藏選項
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. | <kbd>a</kbd>: 全部預存/取消預存
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). | <kbd>&lt;enter&gt;</kbd>: 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. | <kbd>g</kbd>: 檢視上游重設選項
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. | <kbd>D</kbd>: 檢視重設選項
| `` d `` | Discard | View options for discarding changes to the selected file. | <kbd>`</kbd>: 切換檔案樹狀視圖
| `` g `` | 檢視上游重設選項 | | <kbd>M</kbd>: 開啟外部合併工具 (git mergetool)
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). | <kbd>f</kbd>: 擷取
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. | <kbd>/</kbd>: 開始搜尋
| `` <c-t> `` | Open external diff tool (git difftool) | | </pre>
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 擷取 | Fetch changes from remote. |
| `` / `` | 搜尋 | |
## 狀態 ## 狀態
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>o</kbd>: 開啟設定檔案
| `` o `` | 開啟設定檔案 | Open file in default application. | <kbd>e</kbd>: 編輯設定檔案
| `` e `` | 編輯設定檔案 | Open file in external editor. | <kbd>u</kbd>: 檢查更新
| `` u `` | 檢查更新 | | <kbd>&lt;enter&gt;</kbd>: 切換到最近使用的版本庫
| `` <enter> `` | 切換到最近使用的版本庫 | | <kbd>a</kbd>: 顯示所有分支日誌
| `` a `` | 顯示所有分支日誌 | | </pre>
## 確認面板 ## 確認面板
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;enter&gt;</kbd>: 確認
| `` <enter> `` | 確認 | | <kbd>&lt;esc&gt;</kbd>: 關閉/取消
| `` <esc> `` | 關閉/取消 | | </pre>
## 遠端 ## 遠端
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>f</kbd>: 擷取遠端
| `` <enter> `` | View branches | | <kbd>n</kbd>: 新增遠端
| `` n `` | 新增遠端 | | <kbd>d</kbd>: 移除遠端
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. | <kbd>e</kbd>: 編輯遠端
| `` e `` | Edit | 編輯遠端 | <kbd>/</kbd>: Filter the current view by text
| `` f `` | 擷取 | 擷取遠端 | </pre>
| `` / `` | 搜尋 | |
## 遠端分支 ## 遠端分支
| Key | Action | Info | <pre>
|-----|--------|-------------| <kbd>&lt;c-o&gt;</kbd>: 複製分支名稱到剪貼簿
| `` <c-o> `` | 複製分支名稱到剪貼簿 | | <kbd>&lt;space&gt;</kbd>: 檢出
| `` <space> `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. | <kbd>n</kbd>: 新分支
| `` n `` | 新分支 | | <kbd>M</kbd>: 合併到當前檢出的分支
| `` M `` | 合併到當前檢出的分支 | Merge selected branch into currently checked out branch. | <kbd>r</kbd>: 將已檢出的分支變基至此分支
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. | <kbd>d</kbd>: 刪除分支
| `` d `` | Delete | Delete the remote branch from the remote. | <kbd>u</kbd>: 將此分支設為當前分支之上游
| `` u `` | Set as upstream | 將此分支設為當前分支之上游 | <kbd>g</kbd>: 檢視重設選項
| `` s `` | Sort order | | <kbd>&lt;enter&gt;</kbd>: 檢視提交
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. | <kbd>/</kbd>: Filter the current view by text
| `` <enter> `` | 檢視提交 | | </pre>
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
+18 -26
View File
@@ -1,82 +1,74 @@
module github.com/jesseduffield/lazygit module github.com/jesseduffield/lazygit
go 1.22 go 1.18
require ( require (
github.com/adrg/xdg v0.4.0 github.com/OpenPeeDeeP/xdg v1.0.0
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/aybabtme/humanlog v0.4.1 github.com/aybabtme/humanlog v0.4.1
github.com/cli/safeexec v1.0.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11 github.com/creack/pty v1.1.11
github.com/gdamore/tcell/v2 v2.7.4 github.com/fsmiamoto/git-todo-parser v0.0.5
github.com/go-errors/errors v1.5.1 github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell/v2 v2.6.0
github.com/go-errors/errors v1.4.2
github.com/gookit/color v1.4.2 github.com/gookit/color v1.4.2
github.com/iancoleman/orderedmap v0.3.0
github.com/imdario/mergo v0.3.11 github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0 github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513 github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
github.com/kyokomi/emoji/v2 v2.2.8 github.com/kyokomi/emoji/v2 v2.2.8
github.com/lucasb-eyer/go-colorful v1.2.0 github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.15 github.com/mattn/go-runewidth v0.0.14
github.com/mgutz/str v1.2.0 github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0 github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0 github.com/sahilm/fuzzy v0.1.0
github.com/samber/lo v1.31.0 github.com/samber/lo v1.31.0
github.com/sanity-io/litter v1.5.2 github.com/sanity-io/litter v1.5.2
github.com/sasha-s/go-deadlock v0.3.1 github.com/sasha-s/go-deadlock v0.3.1
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.9.5
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2 github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
golang.org/x/sync v0.7.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.0.0 // indirect github.com/go-git/go-billy/v5 v5.0.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/google/go-cmp v0.5.6 // indirect github.com/google/go-cmp v0.5.6 // indirect
github.com/invopop/jsonschema v0.10.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/ginkgo v1.10.3 // indirect
github.com/onsi/gomega v1.7.1 // indirect github.com/onsi/gomega v1.7.1 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.19.0 // indirect golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.11.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )
+34 -456
View File
@@ -1,45 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/OpenPeeDeeP/xdg v1.0.0 h1:UDLmNjCGFZZCaVMB74DqYEtXkHxnTxcr4FeJVF9uCn8=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/OpenPeeDeeP/xdg v1.0.0/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
@@ -51,20 +11,10 @@ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn
github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek= github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek=
github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo= github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -74,122 +24,56 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM= github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM=
github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI= github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
github.com/invopop/jsonschema v0.10.0 h1:c1ktzNLBun3LyQQhyty5WE3lulbOdIIyOVlkmDLehcE=
github.com/invopop/jsonschema v0.10.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8TIcC6Y4RI+1ZbJDOHfGJ570tPeYVCqo7/tws= github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8TIcC6Y4RI+1ZbJDOHfGJ570tPeYVCqo7/tws=
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513 h1:Y1bw5iItrsDCumATc/rklIJ/6K+68ieiWZJedhrNuXo= github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f h1:w/pxI34XepTAx4HwxUu8ipimbVRgSTS+7ahmgFQwH80=
github.com/jesseduffield/gocui v0.3.1-0.20240418080333-8cd33929c513/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -197,20 +81,13 @@ github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:q
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U= github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I= github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3 h1:s995u+gNQADMaixtNOs+jilRC/Q78q0UXSI7+4T0cDE=
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3/go.mod h1:MCbEh21gjOzxc31udr3u4QM9DAdf8TFJCZz3u5hYIxA=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -222,10 +99,9 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
@@ -235,14 +111,13 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw= github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w= github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -252,19 +127,16 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM= github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
@@ -277,372 +149,88 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2 h1:RTNWemd/9z9A5L/AggEP3OdhRz5dXETB/wdAlYF0SuM=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2/go.mod h1:HFt9hGqMzgQ+gVxMKcvTvGaFz4Y0yYycqqAp2V3wcJY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU= golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 h1:s/+U+w0teGzcoH2mdIlFQ6KfVKGaYpgyGdUefZrn9TU=
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 h1:KzcWKJ0nMAmGoBhYVMnkWc1rXjB42lKy5aIys4TdLOA= gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 h1:KzcWKJ0nMAmGoBhYVMnkWc1rXjB42lKy5aIys4TdLOA=
@@ -658,13 +246,3 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+7 -15
View File
@@ -11,8 +11,8 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/jesseduffield/generics/slices"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types" appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -23,7 +23,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui" "github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs" "github.com/jesseduffield/lazygit/pkg/logs"
"github.com/jesseduffield/lazygit/pkg/updates" "github.com/jesseduffield/lazygit/pkg/updates"
) )
@@ -43,7 +42,7 @@ func Run(
common *common.Common, common *common.Common,
startArgs appTypes.StartArgs, startArgs appTypes.StartArgs,
) { ) {
app, err := NewApp(config, startArgs.IntegrationTest, common) app, err := NewApp(config, common)
if err == nil { if err == nil {
err = app.Run(startArgs) err = app.Run(startArgs)
@@ -63,7 +62,6 @@ func Run(
func NewCommon(config config.AppConfigurer) (*common.Common, error) { func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig() userConfig := config.GetUserConfig()
appState := config.GetAppState()
var err error var err error
log := newLogger(config) log := newLogger(config)
@@ -76,9 +74,7 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
Log: log, Log: log,
Tr: tr, Tr: tr,
UserConfig: userConfig, UserConfig: userConfig,
AppState: appState,
Debug: config.GetDebug(), Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}, nil }, nil
} }
@@ -95,7 +91,7 @@ func newLogger(cfg config.AppConfigurer) *logrus.Entry {
} }
// NewApp bootstrap a new application // NewApp bootstrap a new application
func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest, common *common.Common) (*App, error) { func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
app := &App{ app := &App{
closers: []io.Closer{}, closers: []io.Closer{},
Config: config, Config: config,
@@ -129,7 +125,7 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
showRecentRepos = true showRecentRepos = true
} }
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName, test) app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
if err != nil { if err != nil {
return app, err return app, err
} }
@@ -269,11 +265,7 @@ func (app *App) Run(startArgs appTypes.StartArgs) error {
// Close closes any resources // Close closes any resources
func (app *App) Close() error { func (app *App) Close() error {
for _, closer := range app.closers { return slices.TryForEach(app.closers, func(closer io.Closer) error {
if err := closer.Close(); err != nil { return closer.Close()
return err })
}
}
return nil
} }
+44 -108
View File
@@ -5,14 +5,14 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec"
"strconv" "strconv"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stefanhaller/git-todo-parser/todo"
) )
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process. // Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
@@ -33,14 +33,12 @@ const (
DaemonKindUnknown DaemonKind = iota DaemonKindUnknown DaemonKind = iota
DaemonKindExitImmediately DaemonKindExitImmediately
DaemonKindRemoveUpdateRefsForCopiedBranch
DaemonKindCherryPick DaemonKindCherryPick
DaemonKindMoveTodosUp DaemonKindMoveTodoUp
DaemonKindMoveTodosDown DaemonKindMoveTodoDown
DaemonKindInsertBreak DaemonKindInsertBreak
DaemonKindChangeTodoActions DaemonKindChangeTodoActions
DaemonKindMoveFixupCommitDown DaemonKindMoveFixupCommitDown
DaemonKindWriteRebaseTodo
) )
const ( const (
@@ -54,15 +52,13 @@ func getInstruction() Instruction {
jsonData := os.Getenv(DaemonInstructionEnvKey) jsonData := os.Getenv(DaemonInstructionEnvKey)
mapping := map[DaemonKind]func(string) Instruction{ mapping := map[DaemonKind]func(string) Instruction{
DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction], DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction],
DaemonKindRemoveUpdateRefsForCopiedBranch: deserializeInstruction[*RemoveUpdateRefsForCopiedBranchInstruction], DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction], DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction], DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction], DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction], DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction], DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
DaemonKindWriteRebaseTodo: deserializeInstruction[*WriteRebaseTodoInstruction],
} }
return mapping[getDaemonKind()](jsonData) return mapping[getDaemonKind()](jsonData)
@@ -96,7 +92,7 @@ func getDaemonKind() DaemonKind {
} }
func getCommentChar() byte { func getCommentChar() byte {
cmd := exec.Command("git", "config", "--get", "--null", "core.commentChar") cmd := secureexec.Command("git", "config", "--get", "--null", "core.commentChar")
if output, err := cmd.Output(); err == nil && len(output) == 2 { if output, err := cmd.Output(); err == nil && len(output) == 2 {
return output[0] return output[0]
} }
@@ -159,26 +155,6 @@ func NewExitImmediatelyInstruction() Instruction {
return &ExitImmediatelyInstruction{} return &ExitImmediatelyInstruction{}
} }
type RemoveUpdateRefsForCopiedBranchInstruction struct{}
func (self *RemoveUpdateRefsForCopiedBranchInstruction) Kind() DaemonKind {
return DaemonKindRemoveUpdateRefsForCopiedBranch
}
func (self *RemoveUpdateRefsForCopiedBranchInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *RemoveUpdateRefsForCopiedBranchInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return nil
})
}
func NewRemoveUpdateRefsForCopiedBranchInstruction() Instruction {
return &RemoveUpdateRefsForCopiedBranchInstruction{}
}
type CherryPickCommitsInstruction struct { type CherryPickCommitsInstruction struct {
Todo string Todo string
} }
@@ -232,30 +208,28 @@ func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error { func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange { for _, c := range self.Changes {
return utils.TodoChange{ if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction, getCommentChar()); err != nil {
Hash: c.Hash, return err
OldAction: todo.Pick,
NewAction: c.NewAction,
} }
}) }
return utils.EditRebaseTodo(path, changes, getCommentChar()) return nil
}) })
} }
// Takes the hash of some commit, and the hash of a fixup commit that was created // Takes the sha of some commit, and the sha of a fixup commit that was created
// at the end of the branch, then moves the fixup commit down to right after the // at the end of the branch, then moves the fixup commit down to right after the
// original commit, changing its type to "fixup" // original commit, changing its type to "fixup"
type MoveFixupCommitDownInstruction struct { type MoveFixupCommitDownInstruction struct {
OriginalHash string OriginalSha string
FixupHash string FixupSha string
} }
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction { func NewMoveFixupCommitDownInstruction(originalSha string, fixupSha string) Instruction {
return &MoveFixupCommitDownInstruction{ return &MoveFixupCommitDownInstruction{
OriginalHash: originalHash, OriginalSha: originalSha,
FixupHash: fixupHash, FixupSha: fixupSha,
} }
} }
@@ -269,69 +243,55 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error { func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar()) return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha, getCommentChar())
}) })
} }
type MoveTodosUpInstruction struct { type MoveTodoUpInstruction struct {
Hashes []string Sha string
} }
func NewMoveTodosUpInstruction(hashes []string) Instruction { func NewMoveTodoUpInstruction(sha string) Instruction {
return &MoveTodosUpInstruction{ return &MoveTodoUpInstruction{
Hashes: hashes, Sha: sha,
} }
} }
func (self *MoveTodosUpInstruction) Kind() DaemonKind { func (self *MoveTodoUpInstruction) Kind() DaemonKind {
return DaemonKindMoveTodosUp return DaemonKindMoveTodoUp
} }
func (self *MoveTodosUpInstruction) SerializedInstructions() string { func (self *MoveTodoUpInstruction) SerializedInstructions() string {
return serializeInstruction(self) return serializeInstruction(self)
} }
func (self *MoveTodosUpInstruction) run(common *common.Common) error { func (self *MoveTodoUpInstruction) run(common *common.Common) error {
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
return utils.Todo{
Hash: hash,
Action: todo.Pick,
}
})
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodosUp(path, todosToMove, getCommentChar()) return utils.MoveTodoUp(path, self.Sha, todo.Pick, getCommentChar())
}) })
} }
type MoveTodosDownInstruction struct { type MoveTodoDownInstruction struct {
Hashes []string Sha string
} }
func NewMoveTodosDownInstruction(hashes []string) Instruction { func NewMoveTodoDownInstruction(sha string) Instruction {
return &MoveTodosDownInstruction{ return &MoveTodoDownInstruction{
Hashes: hashes, Sha: sha,
} }
} }
func (self *MoveTodosDownInstruction) Kind() DaemonKind { func (self *MoveTodoDownInstruction) Kind() DaemonKind {
return DaemonKindMoveTodosDown return DaemonKindMoveTodoDown
} }
func (self *MoveTodosDownInstruction) SerializedInstructions() string { func (self *MoveTodoDownInstruction) SerializedInstructions() string {
return serializeInstruction(self) return serializeInstruction(self)
} }
func (self *MoveTodosDownInstruction) run(common *common.Common) error { func (self *MoveTodoDownInstruction) run(common *common.Common) error {
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
return utils.Todo{
Hash: hash,
Action: todo.Pick,
}
})
return handleInteractiveRebase(common, func(path string) error { return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodosDown(path, todosToMove, getCommentChar()) return utils.MoveTodoDown(path, self.Sha, todo.Pick, getCommentChar())
}) })
} }
@@ -354,27 +314,3 @@ func (self *InsertBreakInstruction) run(common *common.Common) error {
return utils.PrependStrToTodoFile(path, []byte("break\n")) return utils.PrependStrToTodoFile(path, []byte("break\n"))
}) })
} }
type WriteRebaseTodoInstruction struct {
TodosFileContent []byte
}
func NewWriteRebaseTodoInstruction(todosFileContent []byte) Instruction {
return &WriteRebaseTodoInstruction{
TodosFileContent: todosFileContent,
}
}
func (self *WriteRebaseTodoInstruction) Kind() DaemonKind {
return DaemonKindWriteRebaseTodo
}
func (self *WriteRebaseTodoInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *WriteRebaseTodoInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return os.WriteFile(path, self.TodosFileContent, 0o644)
})
}
+6 -11
View File
@@ -5,12 +5,11 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/stefanhaller/git-todo-parser/todo"
) )
type TodoLine struct { type TodoLine struct {
@@ -22,20 +21,20 @@ func (self *TodoLine) ToString() string {
if self.Action == "break" { if self.Action == "break" {
return self.Action + "\n" return self.Action + "\n"
} else { } else {
return self.Action + " " + self.Commit.Hash + " " + self.Commit.Name + "\n" return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
} }
} }
func TodoLinesToString(todoLines []TodoLine) string { func TodoLinesToString(todoLines []TodoLine) string {
lines := lo.Map(todoLines, func(todoLine TodoLine, _ int) string { lines := slices.Map(todoLines, func(todoLine TodoLine) string {
return todoLine.ToString() return todoLine.ToString()
}) })
return strings.Join(lo.Reverse(lines), "") return strings.Join(slices.Reverse(lines), "")
} }
type ChangeTodoAction struct { type ChangeTodoAction struct {
Hash string Sha string
NewAction todo.TodoCommand NewAction todo.TodoCommand
} }
@@ -45,10 +44,6 @@ func handleInteractiveRebase(common *common.Common, f func(path string) error) e
path := os.Args[1] path := os.Args[1]
if strings.HasSuffix(path, "git-rebase-todo") { if strings.HasSuffix(path, "git-rebase-todo") {
err := utils.RemoveUpdateRefsForCopiedBranch(path, getCommentChar())
if err != nil {
return err
}
return f(path) return f(path)
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test } else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG // if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
+16 -30
View File
@@ -4,10 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"net/http"
_ "net/http/pprof"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
@@ -20,6 +17,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs/tail" "github.com/jesseduffield/lazygit/pkg/logs/tail"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -32,7 +30,6 @@ type cliArgs struct {
PrintVersionInfo bool PrintVersionInfo bool
Debug bool Debug bool
TailLogs bool TailLogs bool
Profile bool
PrintDefaultConfig bool PrintDefaultConfig bool
PrintConfigDir bool PrintConfigDir bool
UseConfigDir string UseConfigDir string
@@ -66,17 +63,8 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
log.Fatal(absRepoPath + " is not a valid git repository.") log.Fatal(absRepoPath + " is not a valid git repository.")
} }
cliArgs.WorkTree = absRepoPath
cliArgs.GitDir = filepath.Join(absRepoPath, ".git") cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
err = os.Chdir(absRepoPath)
if err != nil {
log.Fatalf("Failed to change directory to %s: %v", absRepoPath, err)
}
} else if cliArgs.WorkTree != "" {
env.SetWorkTreeEnv(cliArgs.WorkTree)
if err := os.Chdir(cliArgs.WorkTree); err != nil {
log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err)
}
} }
if cliArgs.CustomConfigFile != "" { if cliArgs.CustomConfigFile != "" {
@@ -87,6 +75,10 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir) os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir)
} }
if cliArgs.WorkTree != "" {
env.SetGitWorkTreeEnv(cliArgs.WorkTree)
}
if cliArgs.GitDir != "" { if cliArgs.GitDir != "" {
env.SetGitDirEnv(cliArgs.GitDir) env.SetGitDirEnv(cliArgs.GitDir)
} }
@@ -123,6 +115,12 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
os.Exit(0) os.Exit(0)
} }
if cliArgs.WorkTree != "" {
if err := os.Chdir(cliArgs.WorkTree); err != nil {
log.Fatal(err.Error())
}
}
tempDir, err := os.MkdirTemp("", "lazygit-*") tempDir, err := os.MkdirTemp("", "lazygit-*")
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
@@ -148,14 +146,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
return return
} }
if cliArgs.Profile {
go func() {
if err := http.ListenAndServe("localhost:6060", nil); err != nil {
log.Fatal(err)
}
}()
}
parsedGitArg := parseGitArg(cliArgs.GitArg) parsedGitArg := parseGitArg(cliArgs.GitArg)
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest)) Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
@@ -182,9 +172,6 @@ func parseCliArgsAndEnvVars() *cliArgs {
tailLogs := false tailLogs := false
flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)") flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)")
profile := false
flaggy.Bool(&profile, "", "profile", "Start the profiler and serve it on http port 6060. See CONTRIBUTING.md for more info.")
printDefaultConfig := false printDefaultConfig := false
flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config") flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config")
@@ -194,10 +181,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
useConfigDir := "" useConfigDir := ""
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory") flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
workTree := os.Getenv("GIT_WORK_TREE") workTree := ""
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument") flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
gitDir := os.Getenv("GIT_DIR") gitDir := ""
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument") flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
customConfigFile := "" customConfigFile := ""
@@ -216,7 +203,6 @@ func parseCliArgsAndEnvVars() *cliArgs {
PrintVersionInfo: printVersionInfo, PrintVersionInfo: printVersionInfo,
Debug: debug, Debug: debug,
TailLogs: tailLogs, TailLogs: tailLogs,
Profile: profile,
PrintDefaultConfig: printDefaultConfig, PrintDefaultConfig: printDefaultConfig,
PrintConfigDir: printConfigDir, PrintConfigDir: printConfigDir,
UseConfigDir: useConfigDir, UseConfigDir: useConfigDir,
@@ -278,7 +264,7 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
buildInfo.Commit = revision.Value buildInfo.Commit = revision.Value
// if lazygit was built from source we'll show the version as the // if lazygit was built from source we'll show the version as the
// abbreviated commit hash // abbreviated commit hash
buildInfo.Version = utils.ShortHash(revision.Value) buildInfo.Version = utils.ShortSha(revision.Value)
} }
// if version hasn't been set we assume that neither has the date // if version hasn't been set we assume that neither has the date
@@ -291,7 +277,7 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
} }
func getGitVersionInfo() string { func getGitVersionInfo() string {
cmd := exec.Command("git", "--version") cmd := secureexec.Command("git", "--version")
stdout, _ := cmd.Output() stdout, _ := cmd.Output()
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n") gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
return gitVersion return gitVersion
+3 -7
View File
@@ -3,8 +3,8 @@ package app
import ( import (
"strings" "strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
) )
type errorMapping struct { type errorMapping struct {
@@ -18,7 +18,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
knownErrorMessages := []string{tr.MinGitVersionError} knownErrorMessages := []string{tr.MinGitVersionError}
if lo.Contains(knownErrorMessages, errorMessage) { if slices.Contains(knownErrorMessages, errorMessage) {
return errorMessage, true return errorMessage, true
} }
@@ -27,13 +27,9 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
originalError: "fatal: not a git repository", originalError: "fatal: not a git repository",
newError: tr.NotARepository, newError: tr.NotARepository,
}, },
{
originalError: "getwd: no such file or directory",
newError: tr.WorkingDirectoryDoesNotExist,
},
} }
if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool { if mapping, ok := slices.Find(mappings, func(mapping errorMapping) bool {
return strings.Contains(errorMessage, mapping.originalError) return strings.Contains(errorMessage, mapping.originalError)
}); ok { }); ok {
return mapping.newError, true return mapping.newError, true
+77
View File
@@ -0,0 +1,77 @@
package cheatsheet
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"github.com/pmezard/go-difflib/difflib"
)
func Check() {
dir := GetKeybindingsDir()
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
err := os.RemoveAll(tmpDir)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
err = os.Mkdir(tmpDir, 0o700)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
generateAtDir(tmpDir)
defer os.RemoveAll(tmpDir)
actualContent := obtainContent(dir)
expectedContent := obtainContent(tmpDir)
if expectedContent == "" {
log.Fatal("empty expected content")
}
if actualContent != expectedContent {
err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
A: difflib.SplitLines(expectedContent),
B: difflib.SplitLines(actualContent),
FromFile: "Expected",
FromDate: "",
ToFile: "Actual",
ToDate: "",
Context: 1,
})
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes. If you run the script and no keybindings files are updated as a result, try rebasing onto master and trying again.\n", CommandToRun())
os.Exit(1)
}
fmt.Println("\nCheatsheets are up to date")
}
func obtainContent(dir string) string {
re := regexp.MustCompile(`Keybindings_\w+\.md$`)
content := ""
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if re.MatchString(path) {
bytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
content += string(bytes)
}
return nil
})
if err != nil {
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
}
return content
}
+24 -22
View File
@@ -1,12 +1,10 @@
//go:generate go run generator.go // This "script" generates a file called Keybindings_{{.LANG}}.md
// in current working directory.
// This "script" generates files called Keybindings_{{.LANG}}.md
// in the docs/keybindings directory.
// //
// The content of these generated files is a keybindings cheatsheet. // The content of this generated file is a keybindings cheatsheet.
// //
// To generate the cheatsheets, run: // To generate cheatsheet in english run:
// go generate pkg/cheatsheet/generate.go // go run scripts/generate_cheatsheet.go
package cheatsheet package cheatsheet
@@ -14,8 +12,10 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"github.com/jesseduffield/generics/maps" "github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazycore/pkg/utils" "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/app" "github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
@@ -23,7 +23,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/exp/slices"
) )
type bindingSection struct { type bindingSection struct {
@@ -43,7 +42,7 @@ type headerWithBindings struct {
} }
func CommandToRun() string { func CommandToRun() string {
return "go generate ./..." return "go run scripts/cheatsheet/main.go generate"
} }
func GetKeybindingsDir() string { func GetKeybindingsDir() string {
@@ -60,7 +59,7 @@ func generateAtDir(cheatsheetDir string) {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
mApp, _ := app.NewApp(mConfig, nil, common) mApp, _ := app.NewApp(mConfig, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md" path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path) file, err := os.Create(path)
if err != nil { if err != nil {
@@ -102,7 +101,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"reflogCommits": tr.ReflogCommitsTitle, "reflogCommits": tr.ReflogCommitsTitle,
"tags": tr.TagsTitle, "tags": tr.TagsTitle,
"commitFiles": tr.CommitFilesTitle, "commitFiles": tr.CommitFilesTitle,
"commitMessage": tr.CommitSummaryTitle, "commitMessage": tr.CommitMessageTitle,
"commitDescription": tr.CommitDescriptionTitle, "commitDescription": tr.CommitDescriptionTitle,
"commits": tr.CommitsTitle, "commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle, "confirmation": tr.ConfirmationTitle,
@@ -117,7 +116,6 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"stash": tr.StashTitle, "stash": tr.StashTitle,
"suggestions": tr.SuggestionsCheatsheetTitle, "suggestions": tr.SuggestionsCheatsheetTitle,
"extras": tr.ExtrasTitle, "extras": tr.ExtrasTitle,
"worktrees": tr.WorktreesTitle,
} }
title, ok := contextTitleMap[str] title, ok := contextTitleMap[str]
@@ -130,7 +128,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection { func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"} excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
bindingsToDisplay := lo.Filter(bindings, func(binding *types.Binding, _ int) bool { bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
if lo.Contains(excludedViews, binding.ViewName) { if lo.Contains(excludedViews, binding.ViewName) {
return false return false
} }
@@ -163,7 +161,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
return a.header.title < b.header.title return a.header.title < b.header.title
}) })
return lo.Map(bindingGroups, func(hb headerWithBindings, _ int) *bindingSection { return slices.Map(bindingGroups, func(hb headerWithBindings) *bindingSection {
return &bindingSection{ return &bindingSection{
title: hb.header.title, title: hb.header.title,
bindings: hb.bindings, bindings: hb.bindings,
@@ -190,11 +188,11 @@ func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection)
for _, section := range bindingSections { for _, section := range bindingSections {
content += formatTitle(section.title) content += formatTitle(section.title)
content += "| Key | Action | Info |\n" content += "<pre>\n"
content += "|-----|--------|-------------|\n"
for _, binding := range section.bindings { for _, binding := range section.bindings {
content += formatBinding(binding) content += formatBinding(binding)
} }
content += "</pre>\n"
} }
return content return content
@@ -205,15 +203,19 @@ func formatTitle(title string) string {
} }
func formatBinding(binding *types.Binding) string { func formatBinding(binding *types.Binding) string {
action := keybindings.LabelFromKey(binding.Key) result := fmt.Sprintf(" <kbd>%s</kbd>: %s", escapeAngleBrackets(keybindings.LabelFromKey(binding.Key)), binding.Description)
description := binding.Description
if binding.Alternative != "" { if binding.Alternative != "" {
action += fmt.Sprintf(" (%s)", binding.Alternative) result += fmt.Sprintf(" (%s)", binding.Alternative)
} }
result += "\n"
// Use backticks for keyboard keys. Two backticks are needed with an inner space return result
// to escape a key that is itself a backtick. }
return fmt.Sprintf("| `` %s `` | %s | %s |\n", action, description, binding.Tooltip)
func escapeAngleBrackets(str string) string {
result := strings.ReplaceAll(str, ">", "&gt;")
result = strings.ReplaceAll(result, "<", "&lt;")
return result
} }
func italicize(str string) string { func italicize(str string) string {
-14
View File
@@ -1,14 +0,0 @@
//go:build ignore
package main
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
)
func main() {
fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir())
cheatsheet.Generate()
}
+130 -33
View File
@@ -2,9 +2,11 @@ package commands
import ( import (
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
gogit "github.com/jesseduffield/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -12,12 +14,12 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// GitCommand is our main git interface // GitCommand is our main git interface
type GitCommand struct { type GitCommand struct {
Blame *git_commands.BlameCommands
Branch *git_commands.BranchCommands Branch *git_commands.BranchCommands
Commit *git_commands.CommitCommands Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands Config *git_commands.ConfigCommands
@@ -35,9 +37,6 @@ type GitCommand struct {
Tag *git_commands.TagCommands Tag *git_commands.TagCommands
WorkingTree *git_commands.WorkingTreeCommands WorkingTree *git_commands.WorkingTreeCommands
Bisect *git_commands.BisectCommands Bisect *git_commands.BisectCommands
Worktree *git_commands.WorktreeCommands
Version *git_commands.GitVersion
RepoPaths *git_commands.RepoPaths
Loaders Loaders Loaders Loaders
} }
@@ -51,7 +50,6 @@ type Loaders struct {
RemoteLoader *git_commands.RemoteLoader RemoteLoader *git_commands.RemoteLoader
StashLoader *git_commands.StashLoader StashLoader *git_commands.StashLoader
TagLoader *git_commands.TagLoader TagLoader *git_commands.TagLoader
Worktrees *git_commands.WorktreeLoader
} }
func NewGitCommand( func NewGitCommand(
@@ -59,25 +57,19 @@ func NewGitCommand(
version *git_commands.GitVersion, version *git_commands.GitVersion,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) { ) (*GitCommand, error) {
repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version) if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
if err != nil { return nil, err
return nil, errors.Errorf("Error getting repo paths: %v", err)
} }
err = os.Chdir(repoPaths.WorktreePath()) repo, err := setupRepository(gogit.PlainOpenWithOptions, gogit.PlainOpenOptions{DetectDotGit: false, EnableDotGitCommonDir: true}, cmn.Tr.GitconfigParseErr)
if err != nil { if err != nil {
return nil, utils.WrapError(err) return nil, err
} }
repository, err := gogit.PlainOpenWithOptions( dotGitDir, err := findDotGitDir(os.Stat, os.ReadFile)
repoPaths.WorktreeGitDirPath(),
&gogit.PlainOpenOptions{DetectDotGit: false, EnableDotGitCommonDir: true},
)
if err != nil { if err != nil {
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
return nil, errors.New(cmn.Tr.GitconfigParseErr)
}
return nil, err return nil, err
} }
@@ -86,8 +78,9 @@ func NewGitCommand(
version, version,
osCommand, osCommand,
gitConfig, gitConfig,
repoPaths, dotGitDir,
repository, repo,
syncMutex,
), nil ), nil
} }
@@ -96,8 +89,9 @@ func NewGitCommandAux(
version *git_commands.GitVersion, version *git_commands.GitVersion,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig, gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths, dotGitDir string,
repo *gogit.Repository, repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand { ) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -108,9 +102,9 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands // common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo) configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands) fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands) gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
statusCommands := git_commands.NewStatusCommands(gitCommon) statusCommands := git_commands.NewStatusCommands(gitCommon)
flowCommands := git_commands.NewFlowCommands(gitCommon) flowCommands := git_commands.NewFlowCommands(gitCommon)
remoteCommands := git_commands.NewRemoteCommands(gitCommon) remoteCommands := git_commands.NewRemoteCommands(gitCommon)
@@ -127,24 +121,22 @@ func NewGitCommandAux(
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands) stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
patchBuilder := patch.NewPatchBuilder(cmn.Log, patchBuilder := patch.NewPatchBuilder(cmn.Log,
func(from string, to string, reverse bool, filename string, plain bool) (string, error) { func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain) // TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
// account. For now we just pass false.
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain, false)
}) })
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder) patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon) bisectCommands := git_commands.NewBisectCommands(gitCommon)
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
blameCommands := git_commands.NewBlameCommands(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands) branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd) commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon) commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd) reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes) remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)
stashLoader := git_commands.NewStashLoader(cmn, cmd) stashLoader := git_commands.NewStashLoader(cmn, cmd)
tagLoader := git_commands.NewTagLoader(cmn, cmd) tagLoader := git_commands.NewTagLoader(cmn, cmd)
return &GitCommand{ return &GitCommand{
Blame: blameCommands,
Branch: branchCommands, Branch: branchCommands,
Commit: commitCommands, Commit: commitCommands,
Config: configCommands, Config: configCommands,
@@ -162,8 +154,6 @@ func NewGitCommandAux(
Tag: tagCommands, Tag: tagCommands,
Bisect: bisectCommands, Bisect: bisectCommands,
WorkingTree: workingTreeCommands, WorkingTree: workingTreeCommands,
Worktree: worktreeCommands,
Version: version,
Loaders: Loaders{ Loaders: Loaders{
BranchLoader: branchLoader, BranchLoader: branchLoader,
CommitFileLoader: commitFileLoader, CommitFileLoader: commitFileLoader,
@@ -171,14 +161,121 @@ func NewGitCommandAux(
FileLoader: fileLoader, FileLoader: fileLoader,
ReflogCommitLoader: reflogCommitLoader, ReflogCommitLoader: reflogCommitLoader,
RemoteLoader: remoteLoader, RemoteLoader: remoteLoader,
Worktrees: worktreeLoader,
StashLoader: stashLoader, StashLoader: stashLoader,
TagLoader: tagLoader, TagLoader: tagLoader,
}, },
RepoPaths: repoPaths,
} }
} }
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
gitDir := env.GetGitDirEnv()
if gitDir != "" {
// we've been given the git directory explicitly so no need to navigate to it
_, err := stat(gitDir)
if err != nil {
return utils.WrapError(err)
}
return nil
}
// we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
for {
_, err := stat(".git")
if err == nil {
return nil
}
if !os.IsNotExist(err) {
return utils.WrapError(err)
}
if err = chdir(".."); err != nil {
return utils.WrapError(err)
}
currentPath, err := os.Getwd()
if err != nil {
return err
}
atRoot := currentPath == filepath.Dir(currentPath)
if atRoot {
// we should never really land here: the code that creates GitCommand should
// verify we're in a git directory
return errors.New("Must open lazygit in a git repository")
}
}
}
// resolvePath takes a path containing a symlink and returns the true path
func resolvePath(path string) (string, error) {
l, err := os.Lstat(path)
if err != nil {
return "", err
}
if l.Mode()&os.ModeSymlink == 0 {
return path, nil
}
return filepath.EvalSymlinks(path)
}
func setupRepository(openGitRepository func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error), options gogit.PlainOpenOptions, gitConfigParseErrorStr string) (*gogit.Repository, error) {
unresolvedPath := env.GetGitDirEnv()
if unresolvedPath == "" {
var err error
unresolvedPath, err = os.Getwd()
if err != nil {
return nil, err
}
}
path, err := resolvePath(unresolvedPath)
if err != nil {
return nil, err
}
repository, err := openGitRepository(path, &options)
if err != nil {
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
return nil, errors.New(gitConfigParseErrorStr)
}
return nil, err
}
return repository, err
}
func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
if env.GetGitDirEnv() != "" {
return env.GetGitDirEnv(), nil
}
f, err := stat(".git")
if err != nil {
return "", err
}
if f.IsDir() {
return ".git", nil
}
fileBytes, err := readFile(".git")
if err != nil {
return "", err
}
fileContent := string(fileBytes)
if !strings.HasPrefix(fileContent, "gitdir: ") {
return "", errors.New(".git is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory")
}
return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
}
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run() return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
} }
+16 -29
View File
@@ -19,16 +19,12 @@ func NewBisectCommands(gitCommon *GitCommon) *BisectCommands {
// This command is pretty cheap to run so we're not storing the result anywhere. // This command is pretty cheap to run so we're not storing the result anywhere.
// But if it becomes problematic we can chang that. // But if it becomes problematic we can chang that.
func (self *BisectCommands) GetInfo() *BisectInfo { func (self *BisectCommands) GetInfo() *BisectInfo {
return self.GetInfoForGitDir(self.repoPaths.WorktreeGitDirPath())
}
func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
var err error var err error
info := &BisectInfo{started: false, log: self.Log, newTerm: "bad", oldTerm: "good"} info := &BisectInfo{started: false, log: self.Log, newTerm: "bad", oldTerm: "good"}
// we return nil if we're not in a git bisect session. // we return nil if we're not in a git bisect session.
// we know we're in a session by the presence of a .git/BISECT_START file // we know we're in a session by the presence of a .git/BISECT_START file
bisectStartPath := filepath.Join(gitDir, "BISECT_START") bisectStartPath := filepath.Join(self.dotGitDir, "BISECT_START")
exists, err := self.os.FileExists(bisectStartPath) exists, err := self.os.FileExists(bisectStartPath)
if err != nil { if err != nil {
self.Log.Infof("error getting git bisect info: %s", err.Error()) self.Log.Infof("error getting git bisect info: %s", err.Error())
@@ -48,7 +44,7 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
info.started = true info.started = true
info.start = strings.TrimSpace(string(startContent)) info.start = strings.TrimSpace(string(startContent))
termsContent, err := os.ReadFile(filepath.Join(gitDir, "BISECT_TERMS")) termsContent, err := os.ReadFile(filepath.Join(self.dotGitDir, "BISECT_TERMS"))
if err != nil { if err != nil {
// old git versions won't have this file so we default to bad/good // old git versions won't have this file so we default to bad/good
} else { } else {
@@ -57,7 +53,7 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
info.oldTerm = splitContent[1] info.oldTerm = splitContent[1]
} }
bisectRefsDir := filepath.Join(gitDir, "refs", "bisect") bisectRefsDir := filepath.Join(self.dotGitDir, "refs", "bisect")
files, err := os.ReadDir(bisectRefsDir) files, err := os.ReadDir(bisectRefsDir)
if err != nil { if err != nil {
self.Log.Infof("error getting git bisect info: %s", err.Error()) self.Log.Infof("error getting git bisect info: %s", err.Error())
@@ -76,7 +72,7 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
return info return info
} }
hash := strings.TrimSpace(string(fileContent)) sha := strings.TrimSpace(string(fileContent))
if name == info.newTerm { if name == info.newTerm {
status = BisectStatusNew status = BisectStatusNew
@@ -86,16 +82,16 @@ func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {
status = BisectStatusSkipped status = BisectStatusSkipped
} }
info.statusMap[hash] = status info.statusMap[sha] = status
} }
currentContent, err := os.ReadFile(filepath.Join(gitDir, "BISECT_EXPECTED_REV")) currentContent, err := os.ReadFile(filepath.Join(self.dotGitDir, "BISECT_EXPECTED_REV"))
if err != nil { if err != nil {
self.Log.Infof("error getting git bisect info: %s", err.Error()) self.Log.Infof("error getting git bisect info: %s", err.Error())
return info return info
} }
currentHash := strings.TrimSpace(string(currentContent)) currentSha := strings.TrimSpace(string(currentContent))
info.current = currentHash info.current = currentSha
return info return info
} }
@@ -125,17 +121,8 @@ func (self *BisectCommands) Start() error {
return self.cmd.New(cmdArgs).StreamOutput().Run() return self.cmd.New(cmdArgs).StreamOutput().Run()
} }
func (self *BisectCommands) StartWithTerms(oldTerm string, newTerm string) error {
cmdArgs := NewGitCmd("bisect").Arg("start").
Arg("--term-old=" + oldTerm).
Arg("--term-new=" + newTerm).
ToArgv()
return self.cmd.New(cmdArgs).StreamOutput().Run()
}
// tells us whether we've found our problem commit(s). We return a string slice of // tells us whether we've found our problem commit(s). We return a string slice of
// commit hashes if we're done, and that slice may have more that one item if // commit sha's if we're done, and that slice may have more that one item if
// skipped commits are involved. // skipped commits are involved.
func (self *BisectCommands) IsDone() (bool, []string, error) { func (self *BisectCommands) IsDone() (bool, []string, error) {
info := self.GetInfo() info := self.GetInfo()
@@ -143,8 +130,8 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
return false, nil, nil return false, nil, nil
} }
newHash := info.GetNewHash() newSha := info.GetNewSha()
if newHash == "" { if newSha == "" {
return false, nil, nil return false, nil, nil
} }
@@ -153,14 +140,14 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
done := false done := false
candidates := []string{} candidates := []string{}
cmdArgs := NewGitCmd("rev-list").Arg(newHash).ToArgv() cmdArgs := NewGitCmd("rev-list").Arg(newSha).ToArgv()
err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) { err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) {
hash := strings.TrimSpace(line) sha := strings.TrimSpace(line)
if status, ok := info.statusMap[hash]; ok { if status, ok := info.statusMap[sha]; ok {
switch status { switch status {
case BisectStatusSkipped, BisectStatusNew: case BisectStatusSkipped, BisectStatusNew:
candidates = append(candidates, hash) candidates = append(candidates, sha)
return false, nil return false, nil
case BisectStatusOld: case BisectStatusOld:
done = true done = true
@@ -185,7 +172,7 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
// render the commits from the bad commit. // render the commits from the bad commit.
func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool { func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool {
cmdArgs := NewGitCmd("merge-base"). cmdArgs := NewGitCmd("merge-base").
Arg("--is-ancestor", bisectInfo.GetNewHash(), bisectInfo.GetStartHash()). Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()).
ToArgv() ToArgv()
err := self.cmd.New(cmdArgs).DontLog().Run() err := self.cmd.New(cmdArgs).DontLog().Run()
+12 -12
View File
@@ -2,7 +2,7 @@ package git_commands
import ( import (
"github.com/jesseduffield/generics/maps" "github.com/jesseduffield/generics/maps"
"github.com/samber/lo" "github.com/jesseduffield/generics/slices"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@@ -29,10 +29,10 @@ type BisectInfo struct {
newTerm string // 'bad' by default newTerm string // 'bad' by default
oldTerm string // 'good' by default oldTerm string // 'good' by default
// map of commit hashes to their status // map of commit sha's to their status
statusMap map[string]BisectStatus statusMap map[string]BisectStatus
// the hash of the commit that's under test // the sha of the commit that's under test
current string current string
} }
@@ -49,26 +49,26 @@ func NewNullBisectInfo() *BisectInfo {
return &BisectInfo{started: false} return &BisectInfo{started: false}
} }
func (self *BisectInfo) GetNewHash() string { func (self *BisectInfo) GetNewSha() string {
for hash, status := range self.statusMap { for sha, status := range self.statusMap {
if status == BisectStatusNew { if status == BisectStatusNew {
return hash return sha
} }
} }
return "" return ""
} }
func (self *BisectInfo) GetCurrentHash() string { func (self *BisectInfo) GetCurrentSha() string {
return self.current return self.current
} }
func (self *BisectInfo) GetStartHash() string { func (self *BisectInfo) GetStartSha() string {
return self.start return self.start
} }
func (self *BisectInfo) Status(commitHash string) (BisectStatus, bool) { func (self *BisectInfo) Status(commitSha string) (BisectStatus, bool) {
status, ok := self.statusMap[commitHash] status, ok := self.statusMap[commitSha]
return status, ok return status, ok
} }
@@ -93,9 +93,9 @@ func (self *BisectInfo) Bisecting() bool {
return false return false
} }
if self.GetNewHash() == "" { if self.GetNewSha() == "" {
return false return false
} }
return lo.Contains(maps.Values(self.statusMap), BisectStatusOld) return slices.Contains(maps.Values(self.statusMap), BisectStatusOld)
} }
-33
View File
@@ -1,33 +0,0 @@
package git_commands
import (
"fmt"
)
type BlameCommands struct {
*GitCommon
}
func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
return &BlameCommands{
GitCommon: gitCommon,
}
}
// Blame a range of lines. For each line, output the hash of the commit where
// the line last changed, then a space, then a description of the commit (author
// and date), another space, and then the line. For example:
//
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 11) func NewBlameCommands(gitCommon *GitCommon) *BlameCommands {
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 12) return &BlameCommands{
// ac90ebac688fe8bc2ffd922157a9d2c54681d2aa (Stefan Haller 2023-08-01 14:54:56 +0200 13) GitCommon: gitCommon,
func (self *BlameCommands) BlameLineRange(filename string, commit string, firstLine int, numLines int) (string, error) {
cmdArgs := NewGitCmd("blame").
Arg("-l").
Arg(fmt.Sprintf("-L%d,+%d", firstLine, numLines)).
Arg(commit).
Arg("--").
Arg(filename)
return self.cmd.New(cmdArgs.ToArgv()).RunWithOutput()
}
+5 -31
View File
@@ -28,17 +28,6 @@ func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
// CreateWithUpstream creates a new branch with a given upstream, but without
// checking it out
func (self *BranchCommands) CreateWithUpstream(name string, upstream string) error {
cmdArgs := NewGitCmd("branch").
Arg("--track").
Arg(name, upstream).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// CurrentBranchInfo get the current branch information. // CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New( branchName, err := self.cmd.New(
@@ -65,10 +54,10 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
for _, line := range utils.SplitLines(output) { for _, line := range utils.SplitLines(output) {
split := strings.Split(strings.TrimRight(line, "\r\n"), "\x00") split := strings.Split(strings.TrimRight(line, "\r\n"), "\x00")
if len(split) == 3 && split[0] == "*" { if len(split) == 3 && split[0] == "*" {
hash := split[1] sha := split[1]
displayName := split[2] displayName := split[2]
return BranchInfo{ return BranchInfo{
RefName: hash, RefName: sha,
DisplayName: displayName, DisplayName: displayName,
DetachedHead: true, DetachedHead: true,
}, nil }, nil
@@ -81,23 +70,8 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
}, nil }, nil
} }
// CurrentBranchName get name of current branch // Delete delete branch
func (self *BranchCommands) CurrentBranchName() (string, error) { func (self *BranchCommands) Delete(branch string, force bool) error {
cmdArgs := NewGitCmd("rev-parse").
Arg("--abbrev-ref").
Arg("--verify").
Arg("HEAD").
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err == nil {
return strings.TrimSpace(output), nil
}
return "", err
}
// LocalDelete delete branch locally
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
cmdArgs := NewGitCmd("branch"). cmdArgs := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d"). ArgIfElse(force, "-D", "-d").
Arg(branch). Arg(branch).
@@ -221,7 +195,7 @@ type MergeOpts struct {
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error { func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
cmdArgs := NewGitCmd("merge"). cmdArgs := NewGitCmd("merge").
Arg("--no-edit"). Arg("--no-edit").
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...). ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args).
ArgIf(opts.FastForwardOnly, "--ff-only"). ArgIf(opts.FastForwardOnly, "--ff-only").
Arg(branchName). Arg(branchName).
ToArgv() ToArgv()
+41 -188
View File
@@ -3,19 +3,16 @@ package git_commands
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time"
"github.com/jesseduffield/generics/set" "github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/go-git/v5/config" "github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
) )
// context: // context:
@@ -42,7 +39,6 @@ type BranchInfo struct {
// BranchLoader returns a list of Branch objects for the current repo // BranchLoader returns a list of Branch objects for the current repo
type BranchLoader struct { type BranchLoader struct {
*common.Common *common.Common
*GitCommon
cmd oscommands.ICmdObjBuilder cmd oscommands.ICmdObjBuilder
getCurrentBranchInfo func() (BranchInfo, error) getCurrentBranchInfo func() (BranchInfo, error)
config BranchLoaderConfigCommands config BranchLoaderConfigCommands
@@ -50,14 +46,12 @@ type BranchLoader struct {
func NewBranchLoader( func NewBranchLoader(
cmn *common.Common, cmn *common.Common,
gitCommon *GitCommon,
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
getCurrentBranchInfo func() (BranchInfo, error), getCurrentBranchInfo func() (BranchInfo, error),
config BranchLoaderConfigCommands, config BranchLoaderConfigCommands,
) *BranchLoader { ) *BranchLoader {
return &BranchLoader{ return &BranchLoader{
Common: cmn, Common: cmn,
GitCommon: gitCommon,
cmd: cmd, cmd: cmd,
getCurrentBranchInfo: getCurrentBranchInfo, getCurrentBranchInfo: getCurrentBranchInfo,
config: config, config: config,
@@ -65,49 +59,36 @@ func NewBranchLoader(
} }
// Load the list of branches for the current repo // Load the list of branches for the current repo
func (self *BranchLoader) Load(reflogCommits []*models.Commit, func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
mainBranches *MainBranches, branches := self.obtainBranches()
oldBranches []*models.Branch,
loadBehindCounts bool,
onWorker func(func() error),
renderFunc func(),
) ([]*models.Branch, error) {
branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
if self.AppState.LocalBranchSortOrder == "recency" { reflogBranches := self.obtainReflogBranches(reflogCommits)
reflogBranches := self.obtainReflogBranches(reflogCommits)
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches // loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0) branchesWithRecency := make([]*models.Branch, 0)
outer: outer:
for _, reflogBranch := range reflogBranches { for _, reflogBranch := range reflogBranches {
for j, branch := range branches { for j, branch := range branches {
if branch.Head { if branch.Head {
continue continue
} }
if strings.EqualFold(reflogBranch.Name, branch.Name) { if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch) branchesWithRecency = append(branchesWithRecency, branch)
branches = utils.Remove(branches, j) branches = slices.Remove(branches, j)
continue outer continue outer
}
} }
} }
// Sort branches that don't have a recency value alphabetically
// (we're really doing this for the sake of deterministic behaviour across git versions)
slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) bool {
return a.Name < b.Name
})
branches = utils.Prepend(branches, branchesWithRecency...)
} }
branches = slices.Prepend(branches, branchesWithRecency...)
foundHead := false foundHead := false
for i, branch := range branches { for i, branch := range branches {
if branch.Head { if branch.Head {
foundHead = true foundHead = true
branch.Recency = " *" branch.Recency = " *"
branches = utils.Move(branches, i, 0) branches = slices.Move(branches, i, 0)
break break
} }
} }
@@ -116,7 +97,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
if err != nil { if err != nil {
return nil, err return nil, err
} }
branches = utils.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"}) branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
} }
configBranches, err := self.config.Branches() configBranches, err := self.config.Branches()
@@ -130,109 +111,12 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
branch.UpstreamRemote = match.Remote branch.UpstreamRemote = match.Remote
branch.UpstreamBranch = match.Merge.Short() branch.UpstreamBranch = match.Merge.Short()
} }
// If the branch already existed, take over its BehindBaseBranch value
// to reduce flicker
if oldBranch, found := lo.Find(oldBranches, func(b *models.Branch) bool {
return b.Name == branch.Name
}); found {
branch.BehindBaseBranch.Store(oldBranch.BehindBaseBranch.Load())
}
}
if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
onWorker(func() error {
return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
})
} }
return branches, nil return branches, nil
} }
func (self *BranchLoader) GetBehindBaseBranchValuesForAllBranches( func (self *BranchLoader) obtainBranches() []*models.Branch {
branches []*models.Branch,
mainBranches *MainBranches,
renderFunc func(),
) error {
mainBranchRefs := mainBranches.Get()
if len(mainBranchRefs) == 0 {
return nil
}
t := time.Now()
errg := errgroup.Group{}
for _, branch := range branches {
errg.Go(func() error {
baseBranch, err := self.GetBaseBranch(branch, mainBranches)
if err != nil {
return err
}
behind := 0 // prime it in case something below fails
if baseBranch != "" {
output, err := self.cmd.New(
NewGitCmd("rev-list").
Arg("--left-right").
Arg("--count").
Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), baseBranch)).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
return err
}
// The format of the output is "<ahead>\t<behind>"
aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t")
if len(aheadBehindStr) == 2 {
if value, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
behind = value
}
}
}
branch.BehindBaseBranch.Store(int32(behind))
return nil
})
}
err := errg.Wait()
self.Log.Debugf("time to get behind base branch values for all branches: %s", time.Since(t))
renderFunc()
return err
}
// Find the base branch for the given branch (i.e. the main branch that the
// given branch was forked off of)
//
// Note that this function may return an empty string even if the returned error
// is nil, e.g. when none of the configured main branches exist. This is not
// considered an error condition, so callers need to check both the returned
// error and whether the returned base branch is empty (and possibly react
// differently in both cases).
func (self *BranchLoader) GetBaseBranch(branch *models.Branch, mainBranches *MainBranches) (string, error) {
mergeBase := mainBranches.GetMergeBase(branch.FullRefName())
if mergeBase == "" {
return "", nil
}
output, err := self.cmd.New(
NewGitCmd("for-each-ref").
Arg("--contains").
Arg(mergeBase).
Arg("--format=%(refname)").
Arg(mainBranches.Get()...).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
return "", err
}
trimmedOutput := strings.TrimSpace(output)
split := strings.Split(trimmedOutput, "\n")
if len(split) == 0 || split[0] == "" {
return "", nil
}
return split[0], nil
}
func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch {
output, err := self.getRawBranches() output, err := self.getRawBranches()
if err != nil { if err != nil {
panic(err) panic(err)
@@ -241,7 +125,7 @@ func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch
trimmedOutput := strings.TrimSpace(output) trimmedOutput := strings.TrimSpace(output)
outputLines := strings.Split(trimmedOutput, "\n") outputLines := strings.Split(trimmedOutput, "\n")
return lo.FilterMap(outputLines, func(line string, _ int) (*models.Branch, bool) { return slices.FilterMap(outputLines, func(line string) (*models.Branch, bool) {
if line == "" { if line == "" {
return nil, false return nil, false
} }
@@ -254,8 +138,7 @@ func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch
return nil, false return nil, false
} }
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency" return obtainBranch(split), true
return obtainBranch(split, storeCommitDateAsRecency, canUsePushTrack), true
}) })
} }
@@ -267,18 +150,8 @@ func (self *BranchLoader) getRawBranches() (string, error) {
"%00", "%00",
) )
var sortOrder string
switch strings.ToLower(self.AppState.LocalBranchSortOrder) {
case "recency", "date":
sortOrder = "-committerdate"
case "alphabetical":
sortOrder = "refname"
default:
sortOrder = "refname"
}
cmdArgs := NewGitCmd("for-each-ref"). cmdArgs := NewGitCmd("for-each-ref").
Arg(fmt.Sprintf("--sort=%s", sortOrder)). Arg("--sort=-committerdate").
Arg(fmt.Sprintf("--format=%s", format)). Arg(fmt.Sprintf("--format=%s", format)).
Arg("refs/heads"). Arg("refs/heads").
ToArgv() ToArgv()
@@ -291,50 +164,30 @@ var branchFields = []string{
"refname:short", "refname:short",
"upstream:short", "upstream:short",
"upstream:track", "upstream:track",
"push:track",
"subject", "subject",
"objectname", fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
"committerdate:unix",
} }
// Obtain branch information from parsed line output of getRawBranches() // Obtain branch information from parsed line output of getRawBranches()
func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack bool) *models.Branch { func obtainBranch(split []string) *models.Branch {
headMarker := split[0] headMarker := split[0]
fullName := split[1] fullName := split[1]
upstreamName := split[2] upstreamName := split[2]
track := split[3] track := split[3]
pushTrack := split[4] subject := split[4]
subject := split[5] commitHash := split[5]
commitHash := split[6]
commitDate := split[7]
name := strings.TrimPrefix(fullName, "heads/") name := strings.TrimPrefix(fullName, "heads/")
aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track) pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
var aheadForPush, behindForPush string
if canUsePushTrack {
aheadForPush, behindForPush, _ = parseUpstreamInfo(upstreamName, pushTrack)
} else {
aheadForPush, behindForPush = aheadForPull, behindForPull
}
recency := ""
if storeCommitDateAsRecency {
if unixTimestamp, err := strconv.ParseInt(commitDate, 10, 64); err == nil {
recency = utils.UnixToTimeAgo(unixTimestamp)
}
}
return &models.Branch{ return &models.Branch{
Name: name, Name: name,
Recency: recency, Pushables: pushables,
AheadForPull: aheadForPull, Pullables: pullables,
BehindForPull: behindForPull, UpstreamGone: gone,
AheadForPush: aheadForPush, Head: headMarker == "*",
BehindForPush: behindForPush, Subject: subject,
UpstreamGone: gone, CommitHash: commitHash,
Head: headMarker == "*",
Subject: subject,
CommitHash: commitHash,
} }
} }
@@ -350,10 +203,10 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
return "?", "?", true return "?", "?", true
} }
ahead := parseDifference(track, `ahead (\d+)`) pushables := parseDifference(track, `ahead (\d+)`)
behind := parseDifference(track, `behind (\d+)`) pullables := parseDifference(track, `behind (\d+)`)
return ahead, behind, false return pushables, pullables, false
} }
func parseDifference(track string, regexStr string) string { func parseDifference(track string, regexStr string) string {
+45 -83
View File
@@ -2,9 +2,7 @@ package git_commands
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]" // "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
import ( import (
"strconv"
"testing" "testing"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -12,114 +10,78 @@ import (
func TestObtainBranch(t *testing.T) { func TestObtainBranch(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
input []string input []string
storeCommitDateAsRecency bool expectedBranch *models.Branch
expectedBranch *models.Branch
} }
// Use a time stamp of 2 1/2 hours ago, resulting in a recency string of "2h"
now := time.Now().Unix()
timeStamp := strconv.Itoa(int(now - 2.5*60*60))
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "TrimHeads", testName: "TrimHeads",
input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp}, input: []string{"", "heads/a_branch", "", "", "subject", "123"},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{ expectedBranch: &models.Branch{
Name: "a_branch", Name: "a_branch",
AheadForPull: "?", Pushables: "?",
BehindForPull: "?", Pullables: "?",
AheadForPush: "?", Head: false,
BehindForPush: "?", Subject: "subject",
Head: false, CommitHash: "123",
Subject: "subject",
CommitHash: "123",
}, },
}, },
{ {
testName: "NoUpstream", testName: "NoUpstream",
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp}, input: []string{"", "a_branch", "", "", "subject", "123"},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{ expectedBranch: &models.Branch{
Name: "a_branch", Name: "a_branch",
AheadForPull: "?", Pushables: "?",
BehindForPull: "?", Pullables: "?",
AheadForPush: "?", Head: false,
BehindForPush: "?", Subject: "subject",
Head: false, CommitHash: "123",
Subject: "subject",
CommitHash: "123",
}, },
}, },
{ {
testName: "IsHead", testName: "IsHead",
input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp}, input: []string{"*", "a_branch", "", "", "subject", "123"},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{ expectedBranch: &models.Branch{
Name: "a_branch", Name: "a_branch",
AheadForPull: "?", Pushables: "?",
BehindForPull: "?", Pullables: "?",
AheadForPush: "?", Head: true,
BehindForPush: "?", Subject: "subject",
Head: true, CommitHash: "123",
Subject: "subject",
CommitHash: "123",
}, },
}, },
{ {
testName: "IsBehindAndAhead", testName: "IsBehindAndAhead",
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp}, input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123"},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{ expectedBranch: &models.Branch{
Name: "a_branch", Name: "a_branch",
AheadForPull: "3", Pushables: "3",
BehindForPull: "2", Pullables: "2",
AheadForPush: "3", Head: false,
BehindForPush: "2", Subject: "subject",
Head: false, CommitHash: "123",
Subject: "subject",
CommitHash: "123",
}, },
}, },
{ {
testName: "RemoteBranchIsGone", testName: "RemoteBranchIsGone",
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp}, input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123"},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{ expectedBranch: &models.Branch{
Name: "a_branch", Name: "a_branch",
UpstreamGone: true, UpstreamGone: true,
AheadForPull: "?", Pushables: "?",
BehindForPull: "?", Pullables: "?",
AheadForPush: "?", Head: false,
BehindForPush: "?", Subject: "subject",
Head: false, CommitHash: "123",
Subject: "subject",
CommitHash: "123",
},
},
{
testName: "WithCommitDateAsRecency",
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: true,
expectedBranch: &models.Branch{
Name: "a_branch",
Recency: "2h",
AheadForPull: "?",
BehindForPull: "?",
AheadForPush: "?",
BehindForPush: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
}, },
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
branch := obtainBranch(s.input, s.storeCommitDateAsRecency, true) branch := obtainBranch(s.input)
assert.EqualValues(t, s.expectedBranch, branch) assert.EqualValues(t, s.expectedBranch, branch)
}) })
} }
+6 -14
View File
@@ -41,6 +41,7 @@ func TestBranchGetCommitDifferences(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner}) instance := buildBranchCommands(commonDeps{runner: s.runner})
pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}") pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}")
@@ -88,10 +89,11 @@ func TestBranchDeleteBranch(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner}) instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.LocalDelete("test", s.force)) s.test(instance.Delete("test", s.force))
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
} }
@@ -125,19 +127,6 @@ func TestBranchMerge(t *testing.T) {
branchName: "mybranch", branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--merging-args", "mybranch"}, expected: []string{"merge", "--no-edit", "--merging-args", "mybranch"},
}, },
{
testName: "multiple merging args",
userConfig: &config.UserConfig{
Git: config.GitConfig{
Merging: config.MergingConfig{
Args: "--arg1 --arg2", // it's up to the user what they put here
},
},
},
opts: MergeOpts{},
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--arg1", "--arg2", "mybranch"},
},
{ {
testName: "fast forward only", testName: "fast forward only",
userConfig: &config.UserConfig{}, userConfig: &config.UserConfig{},
@@ -148,6 +137,7 @@ func TestBranchMerge(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expected, "", nil) ExpectGitArgs(s.expected, "", nil)
@@ -187,6 +177,7 @@ func TestBranchCheckout(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner}) instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.Checkout("test", CheckoutOptions{Force: s.force})) s.test(instance.Checkout("test", CheckoutOptions{Force: s.force}))
@@ -275,6 +266,7 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildBranchCommands(commonDeps{runner: s.runner}) instance := buildBranchCommands(commonDeps{runner: s.runner})
s.test(instance.CurrentBranchInfo()) s.test(instance.CurrentBranchInfo())
+41 -124
View File
@@ -38,44 +38,9 @@ func (self *CommitCommands) SetAuthor(value string) error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
// Add a commit's coauthor using Github/Gitlab Co-authored-by metadata. Value is expected to be of the form 'Name <Email>'
func (self *CommitCommands) AddCoAuthor(hash string, author string) error {
message, err := self.GetCommitMessage(hash)
if err != nil {
return err
}
message = AddCoAuthorToMessage(message, author)
cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only", "-m", message).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func AddCoAuthorToMessage(message string, author string) string {
subject, body, _ := strings.Cut(message, "\n")
return strings.TrimSpace(subject) + "\n\n" + AddCoAuthorToDescription(strings.TrimSpace(body), author)
}
func AddCoAuthorToDescription(description string, author string) string {
if description != "" {
lines := strings.Split(description, "\n")
if strings.HasPrefix(lines[len(lines)-1], "Co-authored-by:") {
description += "\n"
} else {
description += "\n\n"
}
}
return description + fmt.Sprintf("Co-authored-by: %s", author)
}
// ResetToCommit reset to commit // ResetToCommit reset to commit
func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars []string) error { func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
cmdArgs := NewGitCmd("reset").Arg("--"+strength, hash).ToArgv() cmdArgs := NewGitCmd("reset").Arg("--"+strength, sha).ToArgv()
return self.cmd.New(cmdArgs). return self.cmd.New(cmdArgs).
// prevents git from prompting us for input which would freeze the program // prevents git from prompting us for input which would freeze the program
@@ -85,13 +50,13 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
Run() Run()
} }
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj { func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description) messageArgs := self.commitMessageArgs(message)
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
cmdArgs := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify"). ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify").
ArgIf(self.signoffFlag() != "", self.signoffFlag()). ArgIf(self.signoffFlag() != "", self.signoffFlag()).
Arg(messageArgs...). Arg(messageArgs...).
ToArgv() ToArgv()
@@ -103,22 +68,9 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv()) return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
} }
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv())
}
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").
Arg("--edit").
Arg("--file="+tmpMessageFile).
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
ToArgv())
}
// RewordLastCommit rewords the topmost commit with the given message // RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(summary string, description string) error { func (self *CommitCommands) RewordLastCommit(message string) error {
messageArgs := self.commitMessageArgs(summary, description) messageArgs := self.commitMessageArgs(message)
cmdArgs := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only"). Arg("--allow-empty", "--amend", "--only").
@@ -128,8 +80,9 @@ func (self *CommitCommands) RewordLastCommit(summary string, description string)
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *CommitCommands) commitMessageArgs(summary string, description string) []string { func (self *CommitCommands) commitMessageArgs(message string) []string {
args := []string{"-m", summary} msg, description, _ := strings.Cut(message, "\n")
args := []string{"-m", msg}
if description != "" { if description != "" {
args = append(args, "-m", description) args = append(args, "-m", description)
@@ -155,28 +108,26 @@ func (self *CommitCommands) signoffFlag() string {
} }
} }
func (self *CommitCommands) GetCommitMessage(commitHash string) (string, error) { // Get the subject of the HEAD commit
cmdArgs := NewGitCmd("log"). func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
Arg("--format=%B", "--max-count=1", commitHash). cmdArgs := NewGitCmd("log").Arg("-1", "--pretty=%s").ToArgv()
Config("log.showsignature=false").
ToArgv()
message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.ReplaceAll(strings.TrimSpace(message), "\r\n", "\n"), err return strings.TrimSpace(message), err
} }
func (self *CommitCommands) GetCommitSubject(commitHash string) (string, error) { func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
cmdArgs := NewGitCmd("log"). cmdArgs := NewGitCmd("rev-list").
Arg("--format=%s", "--max-count=1", commitHash). Arg("--format=%B", "--max-count=1", commitSha).
Config("log.showsignature=false").
ToArgv() ToArgv()
subject, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() messageWithHeader, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.TrimSpace(subject), err message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
return strings.TrimSpace(message), err
} }
func (self *CommitCommands) GetCommitDiff(commitHash string) (string, error) { func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
cmdArgs := NewGitCmd("show").Arg("--no-color", commitHash).ToArgv() cmdArgs := NewGitCmd("show").Arg("--no-color", commitSha).ToArgv()
diff, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() diff, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return diff, err return diff, err
@@ -187,9 +138,9 @@ type Author struct {
Email string Email string
} }
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) { func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash). Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha).
ToArgv() ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
@@ -206,37 +157,23 @@ func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
return author, err return author, err
} }
func (self *CommitCommands) GetCommitMessageFirstLine(hash string) (string, error) { func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error) {
return self.GetCommitMessagesFirstLine([]string{hash}) return self.GetCommitMessagesFirstLine([]string{sha})
} }
func (self *CommitCommands) GetCommitMessagesFirstLine(hashes []string) (string, error) { func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%s"). Arg("--no-patch", "--pretty=format:%s").
Arg(hashes...). Arg(shas...).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
// Example output: func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
//
// cd50c79ae Preserve the commit message correctly even if the description has blank lines
// 3ebba5f32 Add test demonstrating a bug with preserving the commit message
// 9a423c388 Remove unused function
func (self *CommitCommands) GetHashesAndCommitMessagesFirstLine(hashes []string) (string, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%h %s").
Arg(hashes...).
ToArgv()
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
}
func (self *CommitCommands) GetCommitsOneline(hashes []string) (string, error) {
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--oneline"). Arg("--no-patch", "--oneline").
Arg(hashes...). Arg(shas...).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
@@ -255,61 +192,41 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
return self.cmd.New(cmdArgs) return self.cmd.New(cmdArgs)
} }
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj { func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize contextSize := self.UserConfig.Git.DiffContextSize
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg). Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat"). Arg("--stat").
Arg("--decorate"). Arg("--decorate").
Arg("-p"). Arg("-p").
Arg(hash). Arg(sha).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(filterPath != "", "--", filterPath). ArgIf(filterPath != "", "--", filterPath).
Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// Revert reverts the selected commit by hash // Revert reverts the selected commit by sha
func (self *CommitCommands) Revert(hash string) error { func (self *CommitCommands) Revert(sha string) error {
cmdArgs := NewGitCmd("revert").Arg(hash).ToArgv() cmdArgs := NewGitCmd("revert").Arg(sha).ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *CommitCommands) RevertMerge(hash string, parentNumber int) error { func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
cmdArgs := NewGitCmd("revert").Arg(hash, "-m", fmt.Sprintf("%d", parentNumber)). cmdArgs := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
// CreateFixupCommit creates a commit that fixes up a previous commit // CreateFixupCommit creates a commit that fixes up a previous commit
func (self *CommitCommands) CreateFixupCommit(hash string) error { func (self *CommitCommands) CreateFixupCommit(sha string) error {
cmdArgs := NewGitCmd("commit").Arg("--fixup=" + hash).ToArgv() cmdArgs := NewGitCmd("commit").Arg("--fixup=" + sha).ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// CreateAmendCommit creates a commit that changes the commit message of a previous commit
func (self *CommitCommands) CreateAmendCommit(originalSubject, newSubject, newDescription string, includeFileChanges bool) error {
description := newSubject
if newDescription != "" {
description += "\n\n" + newDescription
}
cmdArgs := NewGitCmd("commit").
Arg("-m", "amend! "+originalSubject).
Arg("-m", description).
ArgIf(!includeFileChanges, "--only", "--allow-empty").
ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
@@ -3,6 +3,7 @@ package git_commands
import ( import (
"strings" "strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
@@ -24,7 +25,6 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
// GetFilesInDiff get the specified commit files // GetFilesInDiff get the specified commit files
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) { func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Config("diff.noprefix=false").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff"). Arg("--no-ext-diff").
Arg("--name-status"). Arg("--name-status").
@@ -52,7 +52,7 @@ func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
} }
// typical result looks like 'A my_file' meaning my_file was added // typical result looks like 'A my_file' meaning my_file was added
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile { return slices.Map(lo.Chunk(lines, 2), func(chunk []string) *models.CommitFile {
return &models.CommitFile{ return &models.CommitFile{
ChangeStatus: chunk[0], ChangeStatus: chunk[0],
Name: chunk[1], Name: chunk[1],
+195 -128
View File
@@ -6,18 +6,16 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stefanhaller/git-todo-parser/todo"
) )
// context: // context:
@@ -35,6 +33,11 @@ type CommitLoader struct {
readFile func(filename string) ([]byte, error) readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string dotGitDir string
// List of main branches that exist in the repo.
// We use these to obtain the merge base of the branch.
// When nil, we're yet to obtain the list of existing main branches.
// When an empty slice, we've obtained the list and it's empty.
mainBranches []string
*GitCommon *GitCommon
} }
@@ -42,6 +45,7 @@ type CommitLoader struct {
func NewCommitLoader( func NewCommitLoader(
cmn *common.Common, cmn *common.Common,
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getRebaseMode func() (enums.RebaseMode, error), getRebaseMode func() (enums.RebaseMode, error),
gitCommon *GitCommon, gitCommon *GitCommon,
) *CommitLoader { ) *CommitLoader {
@@ -51,6 +55,8 @@ func NewCommitLoader(
getRebaseMode: getRebaseMode, getRebaseMode: getRebaseMode,
readFile: os.ReadFile, readFile: os.ReadFile,
walkFiles: filepath.Walk, walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
mainBranches: nil,
GitCommon: gitCommon, GitCommon: gitCommon,
} }
} }
@@ -58,15 +64,10 @@ func NewCommitLoader(
type GetCommitsOptions struct { type GetCommitsOptions struct {
Limit bool Limit bool
FilterPath string FilterPath string
FilterAuthor string
IncludeRebaseCommits bool IncludeRebaseCommits bool
RefName string // e.g. "HEAD" or "my_branch" RefName string // e.g. "HEAD" or "my_branch"
RefForPushedStatus string // the ref to use for determining pushed/unpushed status
// determines if we show the whole git graph i.e. pass the '--all' flag // determines if we show the whole git graph i.e. pass the '--all' flag
All bool All bool
// If non-empty, show divergence from this ref (left-right log)
RefToShowDivergenceFrom string
MainBranches *MainBranches
} }
// GetCommits obtains the commits of the current branch // GetCommits obtains the commits of the current branch
@@ -83,81 +84,31 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
commits = append(commits, rebasingCommits...) commits = append(commits, rebasingCommits...)
} }
wg := sync.WaitGroup{}
wg.Add(2)
var logErr error
go utils.Safe(func() {
defer wg.Done()
logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line, opts.RefToShowDivergenceFrom != "")
commits = append(commits, commit)
return false, nil
})
})
var ancestor string
var remoteAncestor string
go utils.Safe(func() {
defer wg.Done()
ancestor = opts.MainBranches.GetMergeBase(opts.RefName)
if opts.RefToShowDivergenceFrom != "" {
remoteAncestor = opts.MainBranches.GetMergeBase(opts.RefToShowDivergenceFrom)
}
})
passedFirstPushedCommit := false passedFirstPushedCommit := false
// I can get this before firstPushedCommit, err := self.getFirstPushedCommit(opts.RefName)
firstPushedCommit, err := self.getFirstPushedCommit(opts.RefForPushedStatus)
if err != nil { if err != nil {
// must have no upstream branch so we'll consider everything as pushed // must have no upstream branch so we'll consider everything as pushed
passedFirstPushedCommit = true passedFirstPushedCommit = true
} }
wg.Wait() err = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line)
if logErr != nil { if commit.Sha == firstPushedCommit {
return nil, logErr
}
for _, commit := range commits {
if commit.Hash == firstPushedCommit {
passedFirstPushedCommit = true passedFirstPushedCommit = true
} }
if commit.Status != models.StatusRebasing { commit.Status = map[bool]models.CommitStatus{true: models.StatusUnpushed, false: models.StatusPushed}[!passedFirstPushedCommit]
if passedFirstPushedCommit { commits = append(commits, commit)
commit.Status = models.StatusPushed return false, nil
} else { })
commit.Status = models.StatusUnpushed if err != nil {
} return nil, err
}
} }
if len(commits) == 0 { if len(commits) == 0 {
return commits, nil return commits, nil
} }
if opts.RefToShowDivergenceFrom != "" { commits = self.setCommitMergedStatuses(opts.RefName, commits)
sort.SliceStable(commits, func(i, j int) bool {
// In the divergence view we want incoming commits to come first
return commits[i].Divergence > commits[j].Divergence
})
_, localSectionStart, found := lo.FindIndexOf(commits, func(commit *models.Commit) bool {
return commit.Divergence == models.DivergenceLeft
})
if !found {
localSectionStart = len(commits)
}
setCommitMergedStatuses(remoteAncestor, commits[:localSectionStart])
setCommitMergedStatuses(ancestor, commits[localSectionStart:])
} else {
setCommitMergedStatuses(ancestor, commits)
}
return commits, nil return commits, nil
} }
@@ -193,24 +144,20 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
return result, nil return result, nil
} }
// extractCommitFromLine takes a line from a git log and extracts the hash, message, date, and tag if present // extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
// then puts them into a commit object // then puts them into a commit object
// example input: // example input:
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag // 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool) *models.Commit { func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
split := strings.SplitN(line, "\x00", 8) split := strings.SplitN(line, "\x00", 7)
hash := split[0] sha := split[0]
unixTimestamp := split[1] unixTimestamp := split[1]
authorName := split[2] authorName := split[2]
authorEmail := split[3] authorEmail := split[3]
extraInfo := strings.TrimSpace(split[4]) extraInfo := strings.TrimSpace(split[4])
parentHashes := split[5] parentHashes := split[5]
divergence := models.DivergenceNone message := split[6]
if showDivergence {
divergence = lo.Ternary(split[6] == "<", models.DivergenceLeft, models.DivergenceRight)
}
message := split[7]
tags := []string{} tags := []string{}
@@ -236,7 +183,7 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
} }
return &models.Commit{ return &models.Commit{
Hash: hash, Sha: sha,
Name: message, Name: message,
Tags: tags, Tags: tags,
ExtraInfo: extraInfo, ExtraInfo: extraInfo,
@@ -244,19 +191,21 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
AuthorName: authorName, AuthorName: authorName,
AuthorEmail: authorEmail, AuthorEmail: authorEmail,
Parents: parents, Parents: parents,
Divergence: divergence,
} }
} }
func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) { func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) {
commits := self.getRebasingCommits(rebaseMode) commits, err := self.getRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(commits) == 0 { if len(commits) == 0 {
return nil, nil return nil, nil
} }
commitHashes := lo.FilterMap(commits, func(commit *models.Commit, _ int) (string, bool) { commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
return commit.Hash, commit.Hash != "" return commit.Sha, commit.Sha != ""
}) })
// note that we're not filtering these as we do non-rebasing commits just because // note that we're not filtering these as we do non-rebasing commits just because
@@ -265,14 +214,14 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
NewGitCmd("show"). NewGitCmd("show").
Config("log.showSignature=false"). Config("log.showSignature=false").
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat). Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
Arg(commitHashes...). Arg(commitShas...).
ToArgv(), ToArgv(),
).DontLog() ).DontLog()
fullCommits := map[string]*models.Commit{} fullCommits := map[string]*models.Commit{}
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line, false) commit := self.extractCommitFromLine(line)
fullCommits[commit.Hash] = commit fullCommits[commit.Sha] = commit
return false, nil return false, nil
}) })
if err != nil { if err != nil {
@@ -280,23 +229,23 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
} }
findFullCommit := lo.Ternary(self.version.IsOlderThan(2, 25, 2), findFullCommit := lo.Ternary(self.version.IsOlderThan(2, 25, 2),
func(hash string) *models.Commit { func(sha string) *models.Commit {
for s, c := range fullCommits { for s, c := range fullCommits {
if strings.HasPrefix(s, hash) { if strings.HasPrefix(s, sha) {
return c return c
} }
} }
return nil return nil
}, },
func(hash string) *models.Commit { func(sha string) *models.Commit {
return fullCommits[hash] return fullCommits[sha]
}) })
hydratedCommits := make([]*models.Commit, 0, len(commits)) hydratedCommits := make([]*models.Commit, 0, len(commits))
for _, rebasingCommit := range commits { for _, rebasingCommit := range commits {
if rebasingCommit.Hash == "" { if rebasingCommit.Sha == "" {
hydratedCommits = append(hydratedCommits, rebasingCommit) hydratedCommits = append(hydratedCommits, rebasingCommit)
} else if commit := findFullCommit(rebasingCommit.Hash); commit != nil { } else if commit := findFullCommit(rebasingCommit.Sha); commit != nil {
commit.Action = rebasingCommit.Action commit.Action = rebasingCommit.Action
commit.Status = rebasingCommit.Status commit.Status = rebasingCommit.Status
hydratedCommits = append(hydratedCommits, commit) hydratedCommits = append(hydratedCommits, commit)
@@ -306,20 +255,73 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
} }
// getRebasingCommits obtains the commits that we're in the process of rebasing // getRebasingCommits obtains the commits that we're in the process of rebasing
func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) {
switch rebaseMode {
case enums.REBASE_MODE_MERGING:
return self.getNormalRebasingCommits()
case enums.REBASE_MODE_INTERACTIVE:
return self.getInteractiveRebasingCommits()
default:
return nil, nil
}
}
func (self *CommitLoader) getNormalRebasingCommits() ([]*models.Commit, error) {
rewrittenCount := 0
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-apply/rewritten"))
if err == nil {
content := string(bytesContent)
rewrittenCount = len(strings.Split(content, "\n"))
}
// we know we're rebasing, so lets get all the files whose names have numbers
commits := []*models.Commit{}
err = self.walkFiles(filepath.Join(self.dotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 {
rewrittenCount--
return nil
}
if err != nil {
return err
}
re := regexp.MustCompile(`^\d+$`)
if !re.MatchString(f.Name()) {
return nil
}
bytesContent, err := self.readFile(path)
if err != nil {
return err
}
content := string(bytesContent)
commit := self.commitFromPatch(content)
commits = append([]*models.Commit{commit}, commits...)
return nil
})
if err != nil {
return nil, err
}
return commits, nil
}
// git-rebase-todo example: // git-rebase-todo example:
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae // pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931 // pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*models.Commit {
if rebaseMode != enums.REBASE_MODE_INTERACTIVE {
return nil
}
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")) // git-rebase-todo.backup example:
// pick 49cbba374296938ea86bbd4bf4fee2f6ba5cccf6 third commit on master
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 blah commit on master
// pick afb893148791a2fbd8091aeb81deba4930c73031 fourth commit on master
// getInteractiveRebasingCommits takes our git-rebase-todo and our git-rebase-todo.backup files
// and extracts out the sha and names of commits that we still have to go
// in the rebase:
func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, error) {
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"))
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
// we assume an error means the file doesn't exist so we just return // we assume an error means the file doesn't exist so we just return
return nil return nil, nil
} }
commits := []*models.Commit{} commits := []*models.Commit{}
@@ -327,14 +329,14 @@ func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*mod
todos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar()) todos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar())
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred while parsing git-rebase-todo file: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred while parsing git-rebase-todo file: %s", err.Error()))
return nil return nil, nil
} }
// See if the current commit couldn't be applied because it conflicted; if // See if the current commit couldn't be applied because it conflicted; if
// so, add a fake entry for it // so, add a fake entry for it
if conflictedCommitHash := self.getConflictedCommit(todos); conflictedCommitHash != "" { if conflictedCommitSha := self.getConflictedCommit(todos); conflictedCommitSha != "" {
commits = append(commits, &models.Commit{ commits = append(commits, &models.Commit{
Hash: conflictedCommitHash, Sha: conflictedCommitSha,
Name: "", Name: "",
Status: models.StatusRebasing, Status: models.StatusRebasing,
Action: models.ActionConflict, Action: models.ActionConflict,
@@ -343,26 +345,24 @@ func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*mod
for _, t := range todos { for _, t := range todos {
if t.Command == todo.UpdateRef { if t.Command == todo.UpdateRef {
t.Msg = t.Ref t.Msg = strings.TrimPrefix(t.Ref, "refs/heads/")
} else if t.Command == todo.Exec {
t.Msg = t.ExecCommand
} else if t.Commit == "" { } else if t.Commit == "" {
// Command does not have a commit associated, skip // Command does not have a commit associated, skip
continue continue
} }
commits = utils.Prepend(commits, &models.Commit{ commits = slices.Prepend(commits, &models.Commit{
Hash: t.Commit, Sha: t.Commit,
Name: t.Msg, Name: t.Msg,
Status: models.StatusRebasing, Status: models.StatusRebasing,
Action: t.Command, Action: t.Command,
}) })
} }
return commits return commits, nil
} }
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string { func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done")) bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/done"))
if err != nil { if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error())) self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error()))
return "" return ""
@@ -375,7 +375,7 @@ func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
} }
amendFileExists := false amendFileExists := false
if _, err := os.Stat(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/amend")); err == nil { if _, err := os.Stat(filepath.Join(self.dotGitDir, "rebase-merge/amend")); err == nil {
amendFileExists = true amendFileExists = true
} }
@@ -448,15 +448,30 @@ func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos [
return lastTodo.Commit return lastTodo.Commit
} }
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) { // assuming the file starts like this:
if ancestor == "" { // From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
return // From: Lazygit Tester <test@example.com>
// Date: Wed, 5 Dec 2018 21:03:23 +1100
// Subject: second commit on master
func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
lines := strings.Split(content, "\n")
sha := strings.Split(lines[0], " ")[1]
name := strings.TrimPrefix(lines[3], "Subject: ")
return &models.Commit{
Sha: sha,
Name: name,
Status: models.StatusRebasing,
} }
}
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
ancestor := self.getMergeBase(refName)
if ancestor == "" {
return commits
}
passedAncestor := false passedAncestor := false
for i, commit := range commits { for i, commit := range commits {
// some commits aren't really commits and don't have hashes, such as the update-ref todo if strings.HasPrefix(ancestor, commit.Sha) {
if commit.Hash != "" && strings.HasPrefix(ancestor, commit.Hash) {
passedAncestor = true passedAncestor = true
} }
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed { if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
@@ -466,6 +481,65 @@ func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
commits[i].Status = models.StatusMerged commits[i].Status = models.StatusMerged
} }
} }
return commits
}
func (self *CommitLoader) getMergeBase(refName string) string {
if self.mainBranches == nil {
self.mainBranches = self.getExistingMainBranches()
}
if len(self.mainBranches) == 0 {
return ""
}
// We pass all configured main branches to the merge-base call; git will
// return the base commit for the closest one.
output, err := self.cmd.New(
NewGitCmd("merge-base").Arg(refName).Arg(self.mainBranches...).
ToArgv(),
).DontLog().RunWithOutput()
if err != nil {
// If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache.
self.mainBranches = nil
}
return ignoringWarnings(output)
}
func (self *CommitLoader) getExistingMainBranches() []string {
return lo.FilterMap(self.UserConfig.Git.MainBranches,
func(branchName string, _ int) (string, bool) {
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
return strings.TrimSpace(ref), true
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
return ref, true
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
return ref, true
}
return "", false
})
} }
func ignoringWarnings(commandOutput string) string { func ignoringWarnings(commandOutput string) string {
@@ -478,7 +552,7 @@ func ignoringWarnings(commandOutput string) string {
return lastLine return lastLine
} }
// getFirstPushedCommit returns the first commit hash which has been pushed to the ref's upstream. // getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream.
// all commits above this are deemed unpushed and marked as such. // all commits above this are deemed unpushed and marked as such.
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) { func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
output, err := self.cmd.New( output, err := self.cmd.New(
@@ -498,25 +572,18 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
// getLog gets the git log. // getLog gets the git log.
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
gitLogOrder := self.AppState.GitLogOrder config := self.UserConfig.Git.Log
refSpec := opts.RefName
if opts.RefToShowDivergenceFrom != "" {
refSpec += "..." + opts.RefToShowDivergenceFrom
}
cmdArgs := NewGitCmd("log"). cmdArgs := NewGitCmd("log").
Arg(refSpec). Arg(opts.RefName).
ArgIf(gitLogOrder != "default", "--"+gitLogOrder). ArgIf(config.Order != "default", "--"+config.Order).
ArgIf(opts.All, "--all"). ArgIf(opts.All, "--all").
Arg("--oneline"). Arg("--oneline").
Arg(prettyFormat). Arg(prettyFormat).
Arg("--abbrev=40"). Arg("--abbrev=40").
ArgIf(opts.FilterAuthor != "", "--author="+opts.FilterAuthor).
ArgIf(opts.Limit, "-300"). ArgIf(opts.Limit, "-300").
ArgIf(opts.FilterPath != "", "--follow"). ArgIf(opts.FilterPath != "", "--follow").
Arg("--no-show-signature"). Arg("--no-show-signature").
ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right").
Arg("--"). Arg("--").
ArgIf(opts.FilterPath != "", opts.FilterPath). ArgIf(opts.FilterPath != "", opts.FilterPath).
ToArgv() ToArgv()
@@ -524,4 +591,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s` const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s`
+58 -110
View File
@@ -1,30 +1,29 @@
package git_commands package git_commands
import ( import (
"errors"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"github.com/go-errors/errors" "github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stefanhaller/git-todo-parser/todo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|>|fix logging b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|fix logging
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|>|refactor e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|refactor
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|>|WIP d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|WIP
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|>|WIP 65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|WIP
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|>|WIP 26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|WIP
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|>|WIP 3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00", -1) 053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00", -1) var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
func TestGetCommits(t *testing.T) { func TestGetCommits(t *testing.T) {
type scenario struct { type scenario struct {
@@ -43,10 +42,10 @@ func TestGetCommits(t *testing.T) {
testName: "should return no commits if there are none", testName: "should return no commits if there are none",
logOrder: "topo-order", logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@@ -55,10 +54,10 @@ func TestGetCommits(t *testing.T) {
testName: "should use proper upstream name for branch", testName: "should use proper upstream name for branch",
logOrder: "topo-order", logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@@ -67,13 +66,13 @@ func TestGetCommits(t *testing.T) {
testName: "should return commits if they are present", testName: "should return commits if they are present",
logOrder: "topo-order", logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop"}, mainBranches: []string{"master", "main", "develop"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches have an upstream // here it's testing which of the configured main branches have an upstream
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
@@ -86,7 +85,7 @@ func TestGetCommits(t *testing.T) {
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode", Name: "better typing for rebase mode",
Status: models.StatusUnpushed, Status: models.StatusUnpushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -100,7 +99,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
Name: "fix logging", Name: "fix logging",
Status: models.StatusPushed, Status: models.StatusPushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -114,7 +113,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c", Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
Name: "refactor", Name: "refactor",
Status: models.StatusPushed, Status: models.StatusPushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -128,7 +127,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "d8084cd558925eb7c9c38afeed5725c21653ab90", Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
Name: "WIP", Name: "WIP",
Status: models.StatusPushed, Status: models.StatusPushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -142,7 +141,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a", Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a",
Name: "WIP", Name: "WIP",
Status: models.StatusPushed, Status: models.StatusPushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -156,7 +155,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "26c07b1ab33860a1a7591a0638f9925ccf497ffa", Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa",
Name: "WIP", Name: "WIP",
Status: models.StatusMerged, Status: models.StatusMerged,
Action: models.ActionNone, Action: models.ActionNone,
@@ -170,7 +169,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "3d4470a6c072208722e5ae9a54bcb9634959a1c5", Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5",
Name: "WIP", Name: "WIP",
Status: models.StatusMerged, Status: models.StatusMerged,
Action: models.ActionNone, Action: models.ActionNone,
@@ -184,7 +183,7 @@ func TestGetCommits(t *testing.T) {
}, },
}, },
{ {
Hash: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2", Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2",
Name: "refactoring the config struct", Name: "refactoring the config struct",
Status: models.StatusMerged, Status: models.StatusMerged,
Action: models.ActionNone, Action: models.ActionNone,
@@ -204,13 +203,13 @@ func TestGetCommits(t *testing.T) {
testName: "should not call merge-base for mainBranches if none exist", testName: "should not call merge-base for mainBranches if none exist",
logOrder: "topo-order", logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"}, mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does // here it's testing which of the configured main branches exist; neither does
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")). ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
@@ -221,7 +220,7 @@ func TestGetCommits(t *testing.T) {
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode", Name: "better typing for rebase mode",
Status: models.StatusUnpushed, Status: models.StatusUnpushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -241,13 +240,13 @@ func TestGetCommits(t *testing.T) {
testName: "should call merge-base for all main branches that exist", testName: "should call merge-base for all main branches that exist",
logOrder: "topo-order", logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"}, mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist // here it's testing which of the configured main branches exist
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
@@ -260,7 +259,7 @@ func TestGetCommits(t *testing.T) {
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "0eea75e8c631fba6b58135697835d58ba4c18dbc", Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode", Name: "better typing for rebase mode",
Status: models.StatusUnpushed, Status: models.StatusUnpushed,
Action: models.ActionNone, Action: models.ActionNone,
@@ -280,10 +279,10 @@ func TestGetCommits(t *testing.T) {
testName: "should not specify order if `log.order` is `default`", testName: "should not specify order if `log.order` is `default`",
logOrder: "default", logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@@ -292,10 +291,10 @@ func TestGetCommits(t *testing.T) {
testName: "should set filter path", testName: "should set filter path",
logOrder: "default", logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"}, opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@@ -303,15 +302,14 @@ func TestGetCommits(t *testing.T) {
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) { t.Run(scenario.testName, func(t *testing.T) {
common := utils.NewDummyCommon() common := utils.NewDummyCommon()
common.AppState = &config.AppState{} common.UserConfig.Git.Log.Order = scenario.logOrder
common.AppState.GitLogOrder = scenario.logOrder
cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
builder := &CommitLoader{ builder := &CommitLoader{
Common: common, Common: common,
cmd: cmd, cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil }, getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git", dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) { readFile: func(filename string) ([]byte, error) {
@@ -323,9 +321,7 @@ func TestGetCommits(t *testing.T) {
} }
common.UserConfig.Git.MainBranches = scenario.mainBranches common.UserConfig.Git.MainBranches = scenario.mainBranches
opts := scenario.opts commits, err := builder.GetCommits(scenario.opts)
opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd)
commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits) assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err) assert.Equal(t, scenario.expectedError, err)
@@ -341,14 +337,14 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
todos []todo.Todo todos []todo.Todo
doneTodos []todo.Todo doneTodos []todo.Todo
amendFileExists bool amendFileExists bool
expectedHash string expectedSha string
}{ }{
{ {
testName: "no done todos", testName: "no done todos",
todos: []todo.Todo{}, todos: []todo.Todo{},
doneTodos: []todo.Todo{}, doneTodos: []todo.Todo{},
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "common case (conflict)", testName: "common case (conflict)",
@@ -364,7 +360,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "fa1afe1", expectedSha: "fa1afe1",
}, },
{ {
testName: "last command was 'break'", testName: "last command was 'break'",
@@ -373,7 +369,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
{Command: todo.Break}, {Command: todo.Break},
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "last command was 'exec'", testName: "last command was 'exec'",
@@ -385,7 +381,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "last command was 'reword'", testName: "last command was 'reword'",
@@ -394,7 +390,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
{Command: todo.Reword}, {Command: todo.Reword},
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "'pick' was rescheduled", testName: "'pick' was rescheduled",
@@ -411,7 +407,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "'pick' was rescheduled, buggy git version", testName: "'pick' was rescheduled, buggy git version",
@@ -436,7 +432,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "conflicting 'pick' after 'exec'", testName: "conflicting 'pick' after 'exec'",
@@ -461,7 +457,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "fa1afe1", expectedSha: "fa1afe1",
}, },
{ {
testName: "'edit' with amend file", testName: "'edit' with amend file",
@@ -473,7 +469,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: true, amendFileExists: true,
expectedHash: "", expectedSha: "",
}, },
{ {
testName: "'edit' without amend file", testName: "'edit' without amend file",
@@ -485,7 +481,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
}, },
amendFileExists: false, amendFileExists: false,
expectedHash: "fa1afe1", expectedSha: "fa1afe1",
}, },
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@@ -505,56 +501,8 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
}, },
} }
hash := builder.getConflictedCommitImpl(scenario.todos, scenario.doneTodos, scenario.amendFileExists) sha := builder.getConflictedCommitImpl(scenario.todos, scenario.doneTodos, scenario.amendFileExists)
assert.Equal(t, scenario.expectedHash, hash) assert.Equal(t, scenario.expectedSha, sha)
})
}
}
func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
type scenario struct {
testName string
commits []*models.Commit
ancestor string
expectedCommits []*models.Commit
}
scenarios := []scenario{
{
testName: "basic",
commits: []*models.Commit{
{Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Hash: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusPushed},
{Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "67890",
expectedCommits: []*models.Commit{
{Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Hash: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusMerged},
{Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusMerged},
},
},
{
testName: "with update-ref",
commits: []*models.Commit{
{Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Hash: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
ancestor: "deadbeef",
expectedCommits: []*models.Commit{
{Hash: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
{Hash: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
{Hash: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.testName, func(t *testing.T) {
expectedCommits := scenario.commits
setCommitMergedStatuses(scenario.ancestor, expectedCommits)
assert.Equal(t, scenario.expectedCommits, expectedCommits)
}) })
} }
} }
+38 -169
View File
@@ -10,30 +10,28 @@ import (
func TestCommitRewordCommit(t *testing.T) { func TestCommitRewordCommit(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
summary string input string
description string
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
"Single line reword", "Single line reword",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil),
"test", "test",
"",
}, },
{ {
"Multi line reword", "Multi line reword",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil),
"test", "test\nline 2\nline 3",
"line 2\nline 3",
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner}) instance := buildCommitCommands(commonDeps{runner: s.runner})
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description)) assert.NoError(t, instance.RewordLastCommit(s.input))
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
} }
@@ -52,8 +50,7 @@ func TestCommitResetToCommit(t *testing.T) {
func TestCommitCommitCmdObj(t *testing.T) { func TestCommitCommitCmdObj(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
summary string message string
description string
configSignoff bool configSignoff bool
configSkipHookPrefix string configSkipHookPrefix string
expectedArgs []string expectedArgs []string
@@ -62,36 +59,35 @@ func TestCommitCommitCmdObj(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "Commit", testName: "Commit",
summary: "test", message: "test",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expectedArgs: []string{"commit", "-m", "test"}, expectedArgs: []string{"commit", "-m", "test"},
}, },
{ {
testName: "Commit with --no-verify flag", testName: "Commit with --no-verify flag",
summary: "WIP: test", message: "WIP: test",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "WIP", configSkipHookPrefix: "WIP",
expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"}, expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"},
}, },
{ {
testName: "Commit with multiline message", testName: "Commit with multiline message",
summary: "line1", message: "line1\nline2",
description: "line2",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"}, expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"},
}, },
{ {
testName: "Commit with signoff", testName: "Commit with signoff",
summary: "test", message: "test",
configSignoff: true, configSignoff: true,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expectedArgs: []string{"commit", "--signoff", "-m", "test"}, expectedArgs: []string{"commit", "--signoff", "-m", "test"},
}, },
{ {
testName: "Commit with signoff and no-verify", testName: "Commit with signoff and no-verify",
summary: "WIP: test", message: "WIP: test",
configSignoff: true, configSignoff: true,
configSkipHookPrefix: "WIP", configSkipHookPrefix: "WIP",
expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"}, expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"},
@@ -99,6 +95,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
userConfig.Git.Commit.SignOff = s.configSignoff userConfig.Git.Commit.SignOff = s.configSignoff
@@ -107,7 +104,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil) runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil)
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner}) instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
assert.NoError(t, instance.CommitCmdObj(s.summary, s.description).Run()) assert.NoError(t, instance.CommitCmdObj(s.message).Run())
runner.CheckForMissingCalls() runner.CheckForMissingCalls()
}) })
} }
@@ -134,6 +131,7 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
userConfig.Git.Commit.SignOff = s.configSignoff userConfig.Git.Commit.SignOff = s.configSignoff
@@ -150,7 +148,7 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
func TestCommitCreateFixupCommit(t *testing.T) { func TestCommitCreateFixupCommit(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
hash string sha string
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
test func(error) test func(error)
} }
@@ -158,7 +156,7 @@ func TestCommitCreateFixupCommit(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "valid case", testName: "valid case",
hash: "12345", sha: "12345",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "--fixup=12345"}, "", nil), ExpectGitArgs([]string{"commit", "--fixup=12345"}, "", nil),
test: func(err error) { test: func(err error) {
@@ -168,59 +166,10 @@ func TestCommitCreateFixupCommit(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner}) instance := buildCommitCommands(commonDeps{runner: s.runner})
s.test(instance.CreateFixupCommit(s.hash)) s.test(instance.CreateFixupCommit(s.sha))
s.runner.CheckForMissingCalls()
})
}
}
func TestCommitCreateAmendCommit(t *testing.T) {
type scenario struct {
testName string
originalSubject string
newSubject string
newDescription string
includeFileChanges bool
runner *oscommands.FakeCmdObjRunner
}
scenarios := []scenario{
{
testName: "subject only",
originalSubject: "original subject",
newSubject: "new subject",
newDescription: "",
includeFileChanges: true,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject"}, "", nil),
},
{
testName: "subject and description",
originalSubject: "original subject",
newSubject: "new subject",
newDescription: "new description",
includeFileChanges: true,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject\n\nnew description"}, "", nil),
},
{
testName: "without file changes",
originalSubject: "original subject",
newSubject: "new subject",
newDescription: "",
includeFileChanges: false,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "-m", "amend! original subject", "-m", "new subject", "--only", "--allow-empty"}, "", nil),
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
err := instance.CreateAmendCommit(s.originalSubject, s.newSubject, s.newDescription, s.includeFileChanges)
assert.NoError(t, err)
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
} }
@@ -232,7 +181,6 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath string filterPath string
contextSize int contextSize int
ignoreWhitespace bool ignoreWhitespace bool
extDiffCmd string
expected []string expected []string
} }
@@ -242,58 +190,41 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath: "", filterPath: "",
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
extDiffCmd: "", expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
}, },
{ {
testName: "Default case with filter path", testName: "Default case with filter path",
filterPath: "file.txt", filterPath: "file.txt",
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
extDiffCmd: "", expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
filterPath: "", filterPath: "",
contextSize: 77, contextSize: 77,
ignoreWhitespace: false, ignoreWhitespace: false,
extDiffCmd: "", expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
}, },
{ {
testName: "Show diff, ignoring whitespace", testName: "Show diff, ignoring whitespace",
filterPath: "", filterPath: "",
contextSize: 77, contextSize: 77,
ignoreWhitespace: true, ignoreWhitespace: true,
extDiffCmd: "", expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
},
{
testName: "Show diff with external diff command",
filterPath: "",
contextSize: 3,
ignoreWhitespace: false,
extDiffCmd: "difft --color=always",
expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd userConfig.Git.DiffContextSize = s.contextSize
appState := &config.AppState{}
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil) runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
repoPaths := RepoPaths{ instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
worktreePath: "/path/to/worktree",
}
instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: appState, runner: runner, repoPaths: &repoPaths})
assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath).Run()) assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath, s.ignoreWhitespace).Run())
runner.CheckForMissingCalls() runner.CheckForMissingCalls()
}) })
} }
@@ -308,17 +239,19 @@ func TestGetCommitMsg(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
"empty", "empty",
``, ` commit deadbeef`,
``, ``,
}, },
{ {
"no line breaks (single line)", "no line breaks (single line)",
`use generics to DRY up context code`, `commit deadbeef
use generics to DRY up context code`,
`use generics to DRY up context code`, `use generics to DRY up context code`,
}, },
{ {
"with line breaks", "with line breaks",
`Merge pull request #1750 from mark2185/fix-issue-template `commit deadbeef
Merge pull request #1750 from mark2185/fix-issue-template
'git-rev parse' should be 'git rev-parse'`, 'git-rev parse' should be 'git rev-parse'`,
`Merge pull request #1750 from mark2185/fix-issue-template `Merge pull request #1750 from mark2185/fix-issue-template
@@ -328,9 +261,10 @@ func TestGetCommitMsg(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{ instance := buildCommitCommands(commonDeps{
runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil), runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
}) })
output, err := instance.GetCommitMessage("deadbeef") output, err := instance.GetCommitMessage("deadbeef")
@@ -351,14 +285,15 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
"Empty message", "Empty message",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1"}, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1"}, "", nil),
func(output string, err error) { func(output string, err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
}, },
{ {
"Default case to retrieve a commit in history", "Default case to retrieve a commit in history",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "hash3 \n", nil).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "hash3"}, `use generics to DRY up context code`, nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "sha3 \n", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "sha3"}, `commit sha3
use generics to DRY up context code`, nil),
func(output string, err error) { func(output string, err error) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "use generics to DRY up context code", output) assert.Equal(t, "use generics to DRY up context code", output)
@@ -367,6 +302,7 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner}) instance := buildCommitCommands(commonDeps{runner: s.runner})
@@ -376,70 +312,3 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
}) })
} }
} }
func TestAddCoAuthorToMessage(t *testing.T) {
scenarios := []struct {
name string
message string
expectedResult string
}{
{
// This never happens, I think it isn't possible to create a commit
// with an empty message. Just including it for completeness.
name: "Empty message",
message: "",
expectedResult: "\n\nCo-authored-by: John Doe <john@doe.com>",
},
{
name: "Just a subject, no body",
message: "Subject",
expectedResult: "Subject\n\nCo-authored-by: John Doe <john@doe.com>",
},
{
name: "Subject and body",
message: "Subject\n\nBody",
expectedResult: "Subject\n\nBody\n\nCo-authored-by: John Doe <john@doe.com>",
},
{
name: "Body already ending with a Co-authored-by line",
message: "Subject\n\nBody\n\nCo-authored-by: Jane Smith <jane@smith.com>",
expectedResult: "Subject\n\nBody\n\nCo-authored-by: Jane Smith <jane@smith.com>\nCo-authored-by: John Doe <john@doe.com>",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
result := AddCoAuthorToMessage(s.message, "John Doe <john@doe.com>")
assert.Equal(t, s.expectedResult, result)
})
}
}
func TestAddCoAuthorToDescription(t *testing.T) {
scenarios := []struct {
name string
description string
expectedResult string
}{
{
name: "Empty description",
description: "",
expectedResult: "Co-authored-by: John Doe <john@doe.com>",
},
{
name: "Non-empty description",
description: "Body",
expectedResult: "Body\n\nCo-authored-by: John Doe <john@doe.com>",
},
{
name: "Description already ending with a Co-authored-by line",
description: "Body\n\nCo-authored-by: Jane Smith <jane@smith.com>",
expectedResult: "Body\n\nCo-authored-by: Jane Smith <jane@smith.com>\nCo-authored-by: John Doe <john@doe.com>",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
result := AddCoAuthorToDescription(s.description, "John Doe <john@doe.com>")
assert.Equal(t, s.expectedResult, result)
})
}
}
+8 -3
View File
@@ -4,6 +4,7 @@ import (
gogit "github.com/jesseduffield/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/sasha-s/go-deadlock"
) )
type GitCommon struct { type GitCommon struct {
@@ -11,9 +12,11 @@ type GitCommon struct {
version *GitVersion version *GitVersion
cmd oscommands.ICmdObjBuilder cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand os *oscommands.OSCommand
repoPaths *RepoPaths dotGitDir string
repo *gogit.Repository repo *gogit.Repository
config *ConfigCommands config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *deadlock.Mutex
} }
func NewGitCommon( func NewGitCommon(
@@ -21,17 +24,19 @@ func NewGitCommon(
version *GitVersion, version *GitVersion,
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
repoPaths *RepoPaths, dotGitDir string,
repo *gogit.Repository, repo *gogit.Repository,
config *ConfigCommands, config *ConfigCommands,
syncMutex *deadlock.Mutex,
) *GitCommon { ) *GitCommon {
return &GitCommon{ return &GitCommon{
Common: cmn, Common: cmn,
version: version, version: version,
cmd: cmd, cmd: cmd,
os: osCommand, os: osCommand,
repoPaths: repoPaths, dotGitDir: dotGitDir,
repo: repo, repo: repo,
config: config, config: config,
syncMutex: syncMutex,
} }
} }
+1 -5
View File
@@ -53,7 +53,7 @@ func (self *ConfigCommands) GetPager(width int) string {
"columnWidth": strconv.Itoa(width/2 - 6), "columnWidth": strconv.Itoa(width/2 - 6),
} }
pagerTemplate := string(self.UserConfig.Git.Paging.Pager) pagerTemplate := self.UserConfig.Git.Paging.Pager
return utils.ResolvePlaceholderString(pagerTemplate, templateValues) return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
} }
@@ -107,7 +107,3 @@ func (self *ConfigCommands) GetCoreCommentChar() byte {
return '#' return '#'
} }
func (self *ConfigCommands) GetRebaseUpdateRefs() bool {
return self.gitConfig.GetBool("rebase.updateRefs")
}
+10 -18
View File
@@ -11,21 +11,18 @@ import (
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spf13/afero"
) )
type commonDeps struct { type commonDeps struct {
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
userConfig *config.UserConfig userConfig *config.UserConfig
appState *config.AppState
gitVersion *GitVersion gitVersion *GitVersion
gitConfig *git_config.FakeGitConfig gitConfig *git_config.FakeGitConfig
getenv func(string) string getenv func(string) string
removeFile func(string) error removeFile func(string) error
dotGitDir string
common *common.Common common *common.Common
cmd *oscommands.CmdObjBuilder cmd *oscommands.CmdObjBuilder
fs afero.Fs
repoPaths *RepoPaths
} }
func buildGitCommon(deps commonDeps) *GitCommon { func buildGitCommon(deps commonDeps) *GitCommon {
@@ -33,17 +30,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common = deps.common gitCommon.Common = deps.common
if gitCommon.Common == nil { if gitCommon.Common == nil {
gitCommon.Common = utils.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState) gitCommon.Common = utils.NewDummyCommonWithUserConfig(deps.userConfig)
}
if deps.fs != nil {
gitCommon.Fs = deps.fs
}
if deps.repoPaths != nil {
gitCommon.repoPaths = deps.repoPaths
} else {
gitCommon.repoPaths = MockRepoPaths(".git")
} }
runner := deps.runner runner := deps.runner
@@ -94,6 +81,11 @@ func buildGitCommon(deps commonDeps) *GitCommon {
TempDir: os.TempDir(), TempDir: os.TempDir(),
}) })
gitCommon.dotGitDir = deps.dotGitDir
if gitCommon.dotGitDir == "" {
gitCommon.dotGitDir = ".git"
}
return gitCommon return gitCommon
} }
@@ -104,7 +96,7 @@ func buildRepo() *gogit.Repository {
} }
func buildFileLoader(gitCommon *GitCommon) *FileLoader { func buildFileLoader(gitCommon *GitCommon) *FileLoader {
return NewFileLoader(gitCommon, gitCommon.cmd, gitCommon.config) return NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
} }
func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands { func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands {
@@ -126,7 +118,7 @@ func buildWorkingTreeCommands(deps commonDeps) *WorkingTreeCommands {
return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader) return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
} }
func buildPatchCommands(deps commonDeps) *PatchCommands { //nolint:golint,unused func buildPatchCommands(deps commonDeps) *PatchCommands {
gitCommon := buildGitCommon(deps) gitCommon := buildGitCommon(deps)
rebaseCommands := buildRebaseCommands(deps) rebaseCommands := buildRebaseCommands(deps)
commitCommands := buildCommitCommands(deps) commitCommands := buildCommitCommands(deps)
@@ -140,7 +132,7 @@ func buildPatchCommands(deps commonDeps) *PatchCommands { //nolint:golint,unused
return NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder) return NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
} }
func buildStatusCommands(deps commonDeps) *StatusCommands { //nolint:golint,unused func buildStatusCommands(deps commonDeps) *StatusCommands {
gitCommon := buildGitCommon(deps) gitCommon := buildGitCommon(deps)
return NewStatusCommands(gitCommon) return NewStatusCommands(gitCommon)
+2 -89
View File
@@ -1,10 +1,6 @@
package git_commands package git_commands
import ( import "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
type DiffCommands struct { type DiffCommands struct {
*GitCommon *GitCommon
@@ -17,90 +13,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
} }
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj { func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != ""
return self.cmd.New( return self.cmd.New(
NewGitCmd("diff"). NewGitCmd("diff").Arg("--submodule", "--no-ext-diff", "--color").Arg(diffArgs...).ToArgv(),
Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath).
ToArgv(),
)
}
func (self *DiffCommands) internalDiffCmdObj(diffArgs ...string) *GitCommandBuilder {
return NewGitCmd("diff").
Config("diff.noprefix=false").
Arg("--no-ext-diff", "--no-color").
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath)
}
func (self *DiffCommands) GetPathDiff(path string, staged bool) (string, error) {
return self.cmd.New(
self.internalDiffCmdObj().
ArgIf(staged, "--staged").
Arg(path).
ToArgv(),
).RunWithOutput()
}
func (self *DiffCommands) GetAllDiff(staged bool) (string, error) {
return self.cmd.New(
self.internalDiffCmdObj().
ArgIf(staged, "--staged").
ToArgv(),
).RunWithOutput()
}
type DiffToolCmdOptions struct {
// The path to show a diff for. Pass "." for the entire repo.
Filepath string
// The commit against which to show the diff. Leave empty to show a diff of
// the working copy.
FromCommit string
// The commit to diff against FromCommit. Leave empty to diff the working
// copy against FromCommit. Leave both FromCommit and ToCommit empty to show
// the diff of the unstaged working copy changes against the index if Staged
// is false, or the staged changes against HEAD if Staged is true.
ToCommit string
// Whether to reverse the left and right sides of the diff.
Reverse bool
// Whether the given Filepath is a directory. We'll pass --dir-diff to
// git-difftool in that case.
IsDirectory bool
// Whether to show the staged or the unstaged changes. Must be false if both
// FromCommit and ToCommit are non-empty.
Staged bool
}
func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("difftool").
Arg("--no-prompt").
ArgIf(opts.IsDirectory, "--dir-diff").
ArgIf(opts.Staged, "--cached").
ArgIf(opts.FromCommit != "", opts.FromCommit).
ArgIf(opts.ToCommit != "", opts.ToCommit).
ArgIf(opts.Reverse, "-R").
Arg("--", opts.Filepath).
ToArgv())
}
func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) oscommands.ICmdObj {
return self.cmd.New(
NewGitCmd("diff-index").
Config("diff.noprefix=false").
Arg("--submodule", "--no-ext-diff", "--no-color", "--patch").
Arg(diffArgs...).ToArgv(),
) )
} }
+7 -22
View File
@@ -8,7 +8,6 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
) )
type FileCommands struct { type FileCommands struct {
@@ -76,25 +75,22 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil return utils.ResolvePlaceholderString(editCmdTemplate, templateValues), nil
} }
func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) { func (self *FileCommands) GetEditCmdStr(filename string) (string, bool) {
// Legacy support for old config; to be removed at some point // Legacy support for old config; to be removed at some point
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" { if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
// If multiple files are selected, we'll simply edit just the first one. if cmdStr, err := self.GetEditCmdStrLegacy(filename, 1); err == nil {
// It's not worth fixing this for the legacy support.
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil {
return cmdStr, true return cmdStr, true
} }
} }
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor) template, editInTerminal := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
templateValues := map[string]string{ templateValues := map[string]string{
"filename": strings.Join(quotedFilenames, " "), "filename": self.cmd.Quote(filename),
} }
cmdStr := utils.ResolvePlaceholderString(template, templateValues) cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr, suspend return cmdStr, editInTerminal
} }
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) { func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
@@ -105,7 +101,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
} }
} }
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor) template, editInTerminal := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{ templateValues := map[string]string{
"filename": self.cmd.Quote(filename), "filename": self.cmd.Quote(filename),
@@ -113,7 +109,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
} }
cmdStr := utils.ResolvePlaceholderString(template, templateValues) cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr, suspend return cmdStr, editInTerminal
} }
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string { func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
@@ -135,17 +131,6 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
return cmdStr return cmdStr
} }
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
templateValues := map[string]string{
"dir": self.cmd.Quote(path),
}
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
return cmdStr, suspend
}
func (self *FileCommands) guessDefaultEditor() string { func (self *FileCommands) guessDefaultEditor() string {
// Try to query a few places where editors get configured // Try to query a few places where editors get configured
editor := self.config.GetCoreEditor() editor := self.config.GetCoreEditor()
+5 -24
View File
@@ -2,11 +2,11 @@ package git_commands
import ( import (
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
) )
type FileLoaderConfig interface { type FileLoaderConfig interface {
@@ -14,15 +14,15 @@ type FileLoaderConfig interface {
} }
type FileLoader struct { type FileLoader struct {
*GitCommon *common.Common
cmd oscommands.ICmdObjBuilder cmd oscommands.ICmdObjBuilder
config FileLoaderConfig config FileLoaderConfig
getFileType func(string) string getFileType func(string) string
} }
func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader { func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader {
return &FileLoader{ return &FileLoader{
GitCommon: gitCommon, Common: cmn,
cmd: cmd, cmd: cmd,
getFileType: oscommands.FileType, getFileType: oscommands.FileType,
config: config, config: config,
@@ -58,32 +58,13 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
Name: status.Name, Name: status.Name,
PreviousName: status.PreviousName, PreviousName: status.PreviousName,
DisplayString: status.StatusString, DisplayString: status.StatusString,
Type: self.getFileType(status.Name),
} }
models.SetStatusFields(file, status.Change) models.SetStatusFields(file, status.Change)
files = append(files, file) files = append(files, file)
} }
// Go through the files to see if any of these files are actually worktrees
// so that we can render them correctly
worktreePaths := linkedWortkreePaths(self.Fs, self.repoPaths.RepoGitDirPath())
for _, file := range files {
for _, worktreePath := range worktreePaths {
absFilePath, err := filepath.Abs(file.Name)
if err != nil {
self.Log.Error(err)
continue
}
if absFilePath == worktreePath {
file.IsWorktree = true
// `git status` renders this worktree as a folder with a trailing slash but we'll represent it as a singular worktree
// If we include the slash, it will be rendered as a folder with a null file inside.
file.Name = strings.TrimSuffix(file.Name, "/")
break
}
}
}
return files return files
} }
+12 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -40,6 +41,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "MM file1.txt", DisplayString: "MM file1.txt",
Type: "file",
ShortStatus: "MM", ShortStatus: "MM",
}, },
{ {
@@ -52,6 +54,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "A file3.txt", DisplayString: "A file3.txt",
Type: "file",
ShortStatus: "A ", ShortStatus: "A ",
}, },
{ {
@@ -64,6 +67,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "AM file2.txt", DisplayString: "AM file2.txt",
Type: "file",
ShortStatus: "AM", ShortStatus: "AM",
}, },
{ {
@@ -76,6 +80,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "?? file4.txt", DisplayString: "?? file4.txt",
Type: "file",
ShortStatus: "??", ShortStatus: "??",
}, },
{ {
@@ -88,6 +93,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: true, HasMergeConflicts: true,
HasInlineMergeConflicts: true, HasInlineMergeConflicts: true,
DisplayString: "UU file5.txt", DisplayString: "UU file5.txt",
Type: "file",
ShortStatus: "UU", ShortStatus: "UU",
}, },
}, },
@@ -107,6 +113,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "MM a\nb.txt", DisplayString: "MM a\nb.txt",
Type: "file",
ShortStatus: "MM", ShortStatus: "MM",
}, },
}, },
@@ -130,6 +137,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "R before1.txt -> after1.txt", DisplayString: "R before1.txt -> after1.txt",
Type: "file",
ShortStatus: "R ", ShortStatus: "R ",
}, },
{ {
@@ -143,6 +151,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "RM before2.txt -> after2.txt", DisplayString: "RM before2.txt -> after2.txt",
Type: "file",
ShortStatus: "RM", ShortStatus: "RM",
}, },
}, },
@@ -165,6 +174,7 @@ func TestFileGetStatusFiles(t *testing.T) {
HasMergeConflicts: false, HasMergeConflicts: false,
HasInlineMergeConflicts: false, HasInlineMergeConflicts: false,
DisplayString: "?? a -> b.txt", DisplayString: "?? a -> b.txt",
Type: "file",
ShortStatus: "??", ShortStatus: "??",
}, },
}, },
@@ -172,11 +182,12 @@ func TestFileGetStatusFiles(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
cmd := oscommands.NewDummyCmdObjBuilder(s.runner) cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
loader := &FileLoader{ loader := &FileLoader{
GitCommon: buildGitCommon(commonDeps{}), Common: utils.NewDummyCommon(),
cmd: cmd, cmd: cmd,
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"}, config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
getFileType: func(string) string { return "file" }, getFileType: func(string) string { return "file" },
+33 -41
View File
@@ -177,44 +177,36 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
} }
} }
func TestEditFilesCmd(t *testing.T) { func TestEditFileCmd(t *testing.T) {
type scenario struct { type scenario struct {
filenames []string filename string
osConfig config.OSConfig osConfig config.OSConfig
expectedCmdStr string expectedCmdStr string
suspend bool expectedEditInTerminal bool
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
filenames: []string{"test"}, filename: "test",
osConfig: config.OSConfig{}, osConfig: config.OSConfig{},
expectedCmdStr: `vim -- "test"`, expectedCmdStr: `vim -- "test"`,
suspend: true, expectedEditInTerminal: true,
}, },
{ {
filenames: []string{"test"}, filename: "test",
osConfig: config.OSConfig{ osConfig: config.OSConfig{
Edit: "nano {{filename}}", Edit: "nano {{filename}}",
}, },
expectedCmdStr: `nano "test"`, expectedCmdStr: `nano "test"`,
suspend: true, expectedEditInTerminal: true,
}, },
{ {
filenames: []string{"file/with space"}, filename: "file/with space",
osConfig: config.OSConfig{ osConfig: config.OSConfig{
EditPreset: "sublime", EditPreset: "sublime",
}, },
expectedCmdStr: `subl -- "file/with space"`, expectedCmdStr: `subl -- "file/with space"`,
suspend: false, expectedEditInTerminal: false,
},
{
filenames: []string{"multiple", "files"},
osConfig: config.OSConfig{
EditPreset: "sublime",
},
expectedCmdStr: `subl -- "multiple" "files"`,
suspend: false,
}, },
} }
@@ -226,28 +218,28 @@ func TestEditFilesCmd(t *testing.T) {
userConfig: userConfig, userConfig: userConfig,
}) })
cmdStr, suspend := instance.GetEditCmdStr(s.filenames) cmdStr, editInTerminal := instance.GetEditCmdStr(s.filename)
assert.Equal(t, s.expectedCmdStr, cmdStr) assert.Equal(t, s.expectedCmdStr, cmdStr)
assert.Equal(t, s.suspend, suspend) assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
} }
} }
func TestEditFileAtLineCmd(t *testing.T) { func TestEditFileAtLineCmd(t *testing.T) {
type scenario struct { type scenario struct {
filename string filename string
lineNumber int lineNumber int
osConfig config.OSConfig osConfig config.OSConfig
expectedCmdStr string expectedCmdStr string
suspend bool expectedEditInTerminal bool
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
filename: "test", filename: "test",
lineNumber: 42, lineNumber: 42,
osConfig: config.OSConfig{}, osConfig: config.OSConfig{},
expectedCmdStr: `vim +42 -- "test"`, expectedCmdStr: `vim +42 -- "test"`,
suspend: true, expectedEditInTerminal: true,
}, },
{ {
filename: "test", filename: "test",
@@ -255,8 +247,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
osConfig: config.OSConfig{ osConfig: config.OSConfig{
EditAtLine: "nano +{{line}} {{filename}}", EditAtLine: "nano +{{line}} {{filename}}",
}, },
expectedCmdStr: `nano +35 "test"`, expectedCmdStr: `nano +35 "test"`,
suspend: true, expectedEditInTerminal: true,
}, },
{ {
filename: "file/with space", filename: "file/with space",
@@ -264,8 +256,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
osConfig: config.OSConfig{ osConfig: config.OSConfig{
EditPreset: "sublime", EditPreset: "sublime",
}, },
expectedCmdStr: `subl -- "file/with space":12`, expectedCmdStr: `subl -- "file/with space":12`,
suspend: false, expectedEditInTerminal: false,
}, },
} }
@@ -277,9 +269,9 @@ func TestEditFileAtLineCmd(t *testing.T) {
userConfig: userConfig, userConfig: userConfig,
}) })
cmdStr, suspend := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber) cmdStr, editInTerminal := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
assert.Equal(t, s.expectedCmdStr, cmdStr) assert.Equal(t, s.expectedCmdStr, cmdStr)
assert.Equal(t, s.suspend, suspend) assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
} }
} }
+2
View File
@@ -23,6 +23,7 @@ func TestStartCmdObj(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{}) instance := buildFlowCommands(commonDeps{})
@@ -68,6 +69,7 @@ func TestFinishCmdObj(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{ instance := buildFlowCommands(commonDeps{
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses), gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
@@ -1,8 +1,6 @@
package git_commands package git_commands
import ( import "strings"
"strings"
)
// convenience struct for building git commands. Especially useful when // convenience struct for building git commands. Especially useful when
// including conditional args // including conditional args
@@ -44,50 +42,9 @@ func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
return self return self
} }
func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder { func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
if condition {
self.Config(ifTrue)
}
return self
}
// the -C arg will make git do a `cd` to the directory before doing anything else
func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
// repo path comes before the command // repo path comes before the command
self.args = append([]string{"-C", path}, self.args...) self.args = append([]string{"-C", value}, self.args...)
return self
}
func (self *GitCommandBuilder) DirIf(condition bool, path string) *GitCommandBuilder {
if condition {
return self.Dir(path)
}
return self
}
// Note, you may prefer to use the Dir method instead of this one
func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder {
// worktree arg comes before the command
self.args = append([]string{"--work-tree", path}, self.args...)
return self
}
// Note, you may prefer to use the Dir method instead of this one
func (self *GitCommandBuilder) GitDir(path string) *GitCommandBuilder {
// git dir arg comes before the command
self.args = append([]string{"--git-dir", path}, self.args...)
return self
}
func (self *GitCommandBuilder) GitDirIf(condition bool, path string) *GitCommandBuilder {
if condition {
return self.GitDir(path)
}
return self return self
} }
@@ -45,7 +45,7 @@ func TestGitCommandBuilder(t *testing.T) {
expected: []string{"git", "-c", "user.email=bar", "-c", "user.name=foo", "push"}, expected: []string{"git", "-c", "user.email=bar", "-c", "user.name=foo", "push"},
}, },
{ {
input: NewGitCmd("push").Dir("a/b/c").ToArgv(), input: NewGitCmd("push").RepoPath("a/b/c").ToArgv(),
expected: []string{"git", "-C", "a/b/c", "push"}, expected: []string{"git", "-C", "a/b/c", "push"},
}, },
} }
-122
View File
@@ -1,122 +0,0 @@
package git_commands
import (
"strings"
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)
type MainBranches struct {
// List of main branches configured by the user. Just the bare names.
configuredMainBranches []string
// Which of these actually exist in the repository. Full ref names, and it
// could be either "refs/heads/..." or "refs/remotes/origin/..." depending
// on which one exists for a given bare name.
existingMainBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
}
func NewMainBranches(
configuredMainBranches []string,
cmd oscommands.ICmdObjBuilder,
) *MainBranches {
return &MainBranches{
configuredMainBranches: configuredMainBranches,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
}
}
// Get the list of main branches that exist in the repository. This is a list of
// full ref names.
func (self *MainBranches) Get() []string {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.existingMainBranches == nil {
self.existingMainBranches = self.determineMainBranches()
}
return self.existingMainBranches
}
// Return the merge base of the given refName with the closest main branch.
func (self *MainBranches) GetMergeBase(refName string) string {
mainBranches := self.Get()
if len(mainBranches) == 0 {
return ""
}
// We pass all existing main branches to the merge-base call; git will
// return the base commit for the closest one.
// We ignore errors from this call, since we can't distinguish whether the
// error is because one of the main branches has been deleted since the last
// call to determineMainBranches, or because the refName has no common
// history with any of the main branches. Since the former should happen
// very rarely, users must quit and restart lazygit to fix it; the latter is
// also not very common, but can totally happen and is not an error.
output, _ := self.cmd.New(
NewGitCmd("merge-base").Arg(refName).Arg(mainBranches...).
ToArgv(),
).DontLog().RunWithOutput()
return ignoringWarnings(output)
}
func (self *MainBranches) determineMainBranches() []string {
var existingBranches []string
var wg sync.WaitGroup
existingBranches = make([]string, len(self.configuredMainBranches))
for i, branchName := range self.configuredMainBranches {
wg.Add(1)
go utils.Safe(func() {
defer wg.Done()
// Try to determine upstream of local main branch
if ref, err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
).DontLog().RunWithOutput(); err == nil {
existingBranches[i] = strings.TrimSpace(ref)
return
}
// If this failed, a local branch for this main branch doesn't exist or it
// has no upstream configured. Try looking for one in the "origin" remote.
ref := "refs/remotes/origin/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
return
}
// If this failed as well, try if we have the main branch as a local
// branch. This covers the case where somebody is using git locally
// for something, but never pushing anywhere.
ref = "refs/heads/" + branchName
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err == nil {
existingBranches[i] = ref
}
})
}
wg.Wait()
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
return branch != ""
})
return existingBranches
}
+13 -18
View File
@@ -1,15 +1,17 @@
package git_commands package git_commands
import ( import (
"fmt"
"path/filepath" "path/filepath"
"time" "time"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/app/daemon" "github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/stefanhaller/git-todo-parser/todo" "github.com/jesseduffield/lazygit/pkg/utils"
) )
type PatchCommands struct { type PatchCommands struct {
@@ -79,7 +81,7 @@ func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts)
} }
func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) { func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) {
filepath := filepath.Join(self.os.GetTempDir(), self.repoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch") filepath := filepath.Join(self.os.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
self.Log.Infof("saving temporary patch to %s", filepath) self.Log.Infof("saving temporary patch to %s", filepath)
if err := self.os.CreateFileWithContent(filepath, patch); err != nil { if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
return "", err return "", err
@@ -157,13 +159,13 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
baseIndex := sourceCommitIdx + 1 baseIndex := sourceCommitIdx + 1
changes := []daemon.ChangeTodoAction{ changes := []daemon.ChangeTodoAction{
{Hash: commits[sourceCommitIdx].Hash, NewAction: todo.Edit}, {Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
{Hash: commits[destinationCommitIdx].Hash, NewAction: todo.Edit}, {Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
} }
self.os.LogCommand(logTodoChanges(changes), false) self.os.LogCommand(logTodoChanges(changes), false)
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: commits[baseIndex].Hash, baseShaOrRoot: commits[baseIndex].Sha,
overrideEditor: true, overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes), instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run() }).Run()
@@ -219,7 +221,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error { func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error {
if stash { if stash {
if err := self.stash.Push(self.Tr.StashPrefix + commits[commitIdx].Hash); err != nil { if err := self.stash.Push(self.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
return err return err
} }
} }
@@ -272,12 +274,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
return self.rebase.ContinueRebase() return self.rebase.ContinueRebase()
} }
func (self *PatchCommands) PullPatchIntoNewCommit( func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx int) error {
commits []*models.Commit,
commitIdx int,
commitSummary string,
commitDescription string,
) error {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil { if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil {
return err return err
} }
@@ -303,7 +300,9 @@ func (self *PatchCommands) PullPatchIntoNewCommit(
return err return err
} }
if err := self.commit.CommitCmdObj(commitSummary, commitDescription).Run(); err != nil { head_message, _ := self.commit.GetHeadCommitMessage()
new_message := fmt.Sprintf("Split from \"%s\"", head_message)
if err := self.commit.CommitCmdObj(new_message).Run(); err != nil {
return err return err
} }
@@ -321,11 +320,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(
// only some lines of a range of adjacent added lines. To solve this, we // only some lines of a range of adjacent added lines. To solve this, we
// get the diff of HEAD and the original commit and then apply that. // get the diff of HEAD and the original commit and then apply that.
func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) { func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) {
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToArgv()
Config("diff.noprefix=false").
Arg("--no-ext-diff").
Arg("HEAD.." + commit.Hash).
ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput() return self.cmd.New(cmdArgs).RunWithOutput()
} }
+68
View File
@@ -0,0 +1,68 @@
package git_commands
import (
"fmt"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
func TestPatchApplyPatch(t *testing.T) {
type scenario struct {
testName string
opts ApplyPatchOpts
runner *oscommands.FakeCmdObjRunner
test func(error)
}
// expectedArgs excludes the last argument which is an indeterminate filename
expectFn := func(expectedArgs []string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) {
args := cmdObj.Args()
assert.Equal(t, len(args), len(expectedArgs)+1, fmt.Sprintf("unexpected command: %s", cmdObj.ToString()))
filename := args[len(args)-1]
content, err := os.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))
return "", errToReturn
}
}
scenarios := []scenario{
{
testName: "valid case",
opts: ApplyPatchOpts{Cached: true},
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn([]string{"git", "apply", "--cached"}, nil)),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "command returns error",
opts: ApplyPatchOpts{Cached: true},
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn([]string{"git", "apply", "--cached"}, errors.New("error"))),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildPatchCommands(commonDeps{runner: s.runner})
s.test(instance.ApplyPatch("test", s.opts))
s.runner.CheckForMissingCalls()
})
}
}
+98 -245
View File
@@ -5,13 +5,14 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/app/daemon" "github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stefanhaller/git-todo-parser/todo"
) )
type RebaseCommands struct { type RebaseCommands struct {
@@ -34,10 +35,10 @@ func NewRebaseCommands(
} }
} }
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error { func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, message string) error {
if models.IsHeadCommit(commits, index) { if models.IsHeadCommit(commits, index) {
// we've selected the top commit so no rebase is required // we've selected the top commit so no rebase is required
return self.commit.RewordLastCommit(summary, description) return self.commit.RewordLastCommit(message)
} }
err := self.BeginInteractiveRebaseForCommit(commits, index, false) err := self.BeginInteractiveRebaseForCommit(commits, index, false)
@@ -46,7 +47,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
} }
// now the selected commit should be our head so we'll amend it with the new message // now the selected commit should be our head so we'll amend it with the new message
err = self.commit.RewordLastCommit(summary, description) err = self.commit.RewordLastCommit(message)
if err != nil { if err != nil {
return err return err
} }
@@ -56,152 +57,115 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) { func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
changes := []daemon.ChangeTodoAction{{ changes := []daemon.ChangeTodoAction{{
Hash: commits[index].Hash, Sha: commits[index].Sha,
NewAction: todo.Reword, NewAction: todo.Reword,
}} }}
self.os.LogCommand(logTodoChanges(changes), false) self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, index+1), baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
instruction: daemon.NewChangeTodoActionsInstruction(changes), instruction: daemon.NewChangeTodoActionsInstruction(changes),
}), nil }), nil
} }
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int) error { func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { return self.GenericAmend(commits, index, func() error {
return self.commit.ResetAuthor() return self.commit.ResetAuthor()
}) })
} }
func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, value string) error { func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int, value string) error {
return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { return self.GenericAmend(commits, index, func() error {
return self.commit.SetAuthor(value) return self.commit.SetAuthor(value)
}) })
} }
func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, start, end int, value string) error { func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
return self.GenericAmend(commits, start, end, func(commit *models.Commit) error { if models.IsHeadCommit(commits, index) {
return self.commit.AddCoAuthor(commit.Hash, value)
})
}
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end int, f func(commit *models.Commit) error) error {
if start == end && models.IsHeadCommit(commits, start) {
// we've selected the top commit so no rebase is required // we've selected the top commit so no rebase is required
return f(commits[start]) return f()
} }
err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, false) err := self.BeginInteractiveRebaseForCommit(commits, index, false)
if err != nil { if err != nil {
return err return err
} }
for commitIndex := end; commitIndex >= start; commitIndex-- { // now the selected commit should be our head so we'll amend it
err = f(commits[commitIndex]) err = f()
if err != nil { if err != nil {
return err return err
}
if err := self.ContinueRebase(); err != nil {
return err
}
} }
return nil return self.ContinueRebase()
} }
func (self *RebaseCommands) MoveCommitsDown(commits []*models.Commit, startIdx int, endIdx int) error { func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+2) baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string { sha := commits[index].Sha
return commit.Hash
}) self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseHashOrRoot, baseShaOrRoot: baseShaOrRoot,
instruction: daemon.NewMoveTodosDownInstruction(hashes), instruction: daemon.NewMoveTodoDownInstruction(sha),
overrideEditor: true, overrideEditor: true,
}).Run() }).Run()
} }
func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int, endIdx int) error { func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
baseHashOrRoot := getBaseHashOrRoot(commits, endIdx+1) baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
hashes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) string { sha := commits[index].Sha
return commit.Hash
}) self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseHashOrRoot, baseShaOrRoot: baseShaOrRoot,
instruction: daemon.NewMoveTodosUpInstruction(hashes), instruction: daemon.NewMoveTodoUpInstruction(sha),
overrideEditor: true, overrideEditor: true,
}).Run() }).Run()
} }
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error { func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
baseIndex := endIdx + 1 baseIndex := index + 1
if action == todo.Squash || action == todo.Fixup { if action == todo.Squash || action == todo.Fixup {
baseIndex++ baseIndex++
} }
baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex) baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
return daemon.ChangeTodoAction{
Hash: commit.Hash,
NewAction: action,
}
})
changes := []daemon.ChangeTodoAction{{
Sha: commits[index].Sha,
NewAction: action,
}}
self.os.LogCommand(logTodoChanges(changes), false) self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseHashOrRoot, baseShaOrRoot: baseShaOrRoot,
overrideEditor: true, overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes), instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run() }).Run()
} }
func (self *RebaseCommands) EditRebase(branchRef string) error { func (self *RebaseCommands) EditRebase(branchRef string) error {
msg := utils.ResolvePlaceholderString( self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
self.Tr.Log.EditRebase,
map[string]string{
"ref": branchRef,
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: branchRef, baseShaOrRoot: branchRef,
instruction: daemon.NewInsertBreakInstruction(), instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func (self *RebaseCommands) EditRebaseFromBaseCommit(targetBranchName string, baseCommit string) error {
msg := utils.ResolvePlaceholderString(
self.Tr.Log.EditRebaseFromBaseCommit,
map[string]string{
"baseCommit": baseCommit,
"targetBranchName": targetBranchName,
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseCommit,
onto: targetBranchName,
instruction: daemon.NewInsertBreakInstruction(),
}).Run() }).Run()
} }
func logTodoChanges(changes []daemon.ChangeTodoAction) string { func logTodoChanges(changes []daemon.ChangeTodoAction) string {
changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string { changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
return fmt.Sprintf("%s:%s", c.Hash, c.NewAction) return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
}), "\n") }), "\n")
return fmt.Sprintf("Changing TODO actions:\n%s", changeTodoStr) return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
} }
type PrepareInteractiveRebaseCommandOpts struct { type PrepareInteractiveRebaseCommandOpts struct {
baseHashOrRoot string baseShaOrRoot string
onto string
instruction daemon.Instruction instruction daemon.Instruction
overrideEditor bool overrideEditor bool
keepCommitsThatBecomeEmpty bool keepCommitsThatBecomeEmpty bool
@@ -209,7 +173,7 @@ type PrepareInteractiveRebaseCommandOpts struct {
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client // we tell git to run lazygit to edit the todo list, and we pass the client
// lazygit instructions what to do with the todo file // lazygit a todo string to write to the todo file
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
ex := oscommands.GetLazygitPath() ex := oscommands.GetLazygitPath()
@@ -217,11 +181,10 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
Arg("--interactive"). Arg("--interactive").
Arg("--autostash"). Arg("--autostash").
Arg("--keep-empty"). Arg("--keep-empty").
ArgIf(opts.keepCommitsThatBecomeEmpty && self.version.IsAtLeast(2, 26, 0), "--empty=keep"). ArgIf(opts.keepCommitsThatBecomeEmpty && !self.version.IsOlderThan(2, 26, 0), "--empty=keep").
Arg("--no-autosquash"). Arg("--no-autosquash").
ArgIf(self.version.IsAtLeast(2, 22, 0), "--rebase-merges"). ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
ArgIf(opts.onto != "", "--onto", opts.onto). Arg(opts.baseShaOrRoot).
Arg(opts.baseHashOrRoot).
ToArgv() ToArgv()
debug := "FALSE" debug := "FALSE"
@@ -238,7 +201,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
if opts.instruction != nil { if opts.instruction != nil {
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...) cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
} else { } else {
cmdObj.AddEnvVars(daemon.ToEnvVars(daemon.NewRemoveUpdateRefsForCopiedBranchInstruction())...) gitSequenceEditor = "true"
} }
cmdObj.AddEnvVars( cmdObj.AddEnvVars(
@@ -255,127 +218,55 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
return cmdObj return cmdObj
} }
// GitRebaseEditTodo runs "git rebase --edit-todo", saving the given todosFileContent to the file
func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
ex := oscommands.GetLazygitPath()
cmdArgs := NewGitCmd("rebase").
Arg("--edit-todo").
ToArgv()
debug := "FALSE"
if self.Debug {
debug = "TRUE"
}
self.Log.WithField("command", cmdArgs).Debug("RunCommand")
cmdObj := self.cmd.New(cmdArgs)
cmdObj.AddEnvVars(daemon.ToEnvVars(daemon.NewWriteRebaseTodoInstruction(todosFileContent))...)
cmdObj.AddEnvVars(
"DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language
"GIT_EDITOR="+ex,
"GIT_SEQUENCE_EDITOR="+ex,
)
return cmdObj.Run()
}
// AmendTo amends the given commit with whatever files are staged // AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error { func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex] commit := commits[commitIndex]
if err := self.commit.CreateFixupCommit(commit.Hash); err != nil { if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
return err return err
} }
// Get the hash of the commit we just created // Get the sha of the commit we just created
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv() cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput() fixupSha, err := self.cmd.New(cmdArgs).RunWithOutput()
if err != nil { if err != nil {
return err return err
} }
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1), baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
overrideEditor: true, overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash), instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Sha, fixupSha),
}).Run() }).Run()
} }
func todoFromCommit(commit *models.Commit) utils.Todo { // EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
if commit.Action == todo.UpdateRef { func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
return utils.Todo{Ref: commit.Name, Action: commit.Action}
} else {
return utils.Todo{Hash: commit.Hash, Action: commit.Action}
}
}
// Sets the action for the given commits in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error {
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
return utils.TodoChange{
Hash: commit.Hash,
OldAction: commit.Action,
NewAction: action,
}
})
return utils.EditRebaseTodo( return utils.EditRebaseTodo(
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action, self.config.GetCoreCommentChar())
commitsWithAction,
self.config.GetCoreCommentChar(),
)
} }
func (self *RebaseCommands) DeleteUpdateRefTodos(commits []*models.Commit) error { // MoveTodoDown moves a rebase todo item down by one position
todosToDelete := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
return todoFromCommit(commit) fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
}) return utils.MoveTodoDown(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
todosFileContent, err := utils.DeleteTodos(
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
todosToDelete,
self.config.GetCoreCommentChar(),
)
if err != nil {
return err
}
return self.GitRebaseEditTodo(todosFileContent)
} }
func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error { // MoveTodoDown moves a rebase todo item down by one position
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo") func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo { fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
return todoFromCommit(commit) return utils.MoveTodoUp(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
})
return utils.MoveTodosDown(fileName, todosToMove, self.config.GetCoreCommentChar())
}
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
todosToMove := lo.Map(commits, func(commit *models.Commit, _ int) utils.Todo {
return todoFromCommit(commit)
})
return utils.MoveTodosUp(fileName, todosToMove, self.config.GetCoreCommentChar())
} }
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one // SquashAllAboveFixupCommits squashes all fixup! commits above the given one
func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) error { func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) error {
hashOrRoot := commit.Hash + "^" shaOrRoot := commit.Sha + "^"
if commit.IsFirstCommit() { if commit.IsFirstCommit() {
hashOrRoot = "--root" shaOrRoot = "--root"
} }
cmdArgs := NewGitCmd("rebase"). cmdArgs := NewGitCmd("rebase").
Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", hashOrRoot). Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot).
ToArgv() ToArgv()
return self.runSkipEditorCommand(self.cmd.New(cmdArgs)) return self.runSkipEditorCommand(self.cmd.New(cmdArgs))
@@ -386,13 +277,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
func (self *RebaseCommands) BeginInteractiveRebaseForCommit( func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool, commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool,
) error { ) error {
return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty) if len(commits)-1 < commitIndex {
}
func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
commits []*models.Commit, start, end int, keepCommitsThatBecomeEmpty bool,
) error {
if len(commits)-1 < end {
return errors.New("index outside of range of commits") return errors.New("index outside of range of commits")
} }
@@ -403,17 +288,14 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
return errors.New(self.Tr.DisabledForGPG) return errors.New(self.Tr.DisabledForGPG)
} }
changes := make([]daemon.ChangeTodoAction, 0, end-start) changes := []daemon.ChangeTodoAction{{
for commitIndex := end; commitIndex >= start; commitIndex-- { Sha: commits[commitIndex].Sha,
changes = append(changes, daemon.ChangeTodoAction{ NewAction: todo.Edit,
Hash: commits[commitIndex].Hash, }}
NewAction: todo.Edit,
})
}
self.os.LogCommand(logTodoChanges(changes), false) self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, end+1), baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
overrideEditor: true, overrideEditor: true,
keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty, keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty,
instruction: daemon.NewChangeTodoActionsInstruction(changes), instruction: daemon.NewChangeTodoActionsInstruction(changes),
@@ -422,14 +304,7 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
// RebaseBranch interactive rebases onto a branch // RebaseBranch interactive rebases onto a branch
func (self *RebaseCommands) RebaseBranch(branchName string) error { func (self *RebaseCommands) RebaseBranch(branchName string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseHashOrRoot: branchName}).Run() return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
}
func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string, baseCommit string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: baseCommit,
onto: targetBranchName,
}).Run()
} }
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
@@ -486,25 +361,23 @@ func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) erro
} }
// DiscardOldFileChanges discards changes to a file from an old commit // DiscardOldFileChanges discards changes to a file from an old commit
func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, filePaths []string) error { func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error {
if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil { if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil {
return err return err
} }
for _, filePath := range filePaths { // check if file exists in previous commit (this command returns an error if the file doesn't exist)
// check if file exists in previous commit (this command returns an error if the file doesn't exist) cmdArgs := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+fileName).ToArgv()
cmdArgs := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+filePath).ToArgv()
if err := self.cmd.New(cmdArgs).Run(); err != nil { if err := self.cmd.New(cmdArgs).Run(); err != nil {
if err := self.os.Remove(filePath); err != nil { if err := self.os.Remove(fileName); err != nil {
return err
}
if err := self.workingTree.StageFile(filePath); err != nil {
return err
}
} else if err := self.workingTree.CheckoutFile("HEAD^", filePath); err != nil {
return err return err
} }
if err := self.workingTree.StageFile(fileName); err != nil {
return err
}
} else if err := self.workingTree.CheckoutFile("HEAD^", fileName); err != nil {
return err
} }
// amend the commit // amend the commit
@@ -517,48 +390,28 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
return self.ContinueRebase() return self.ContinueRebase()
} }
// CherryPickCommits begins an interactive rebase with the given hashes being cherry picked onto HEAD // CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error { func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string { commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
return fmt.Sprintf("%s %s", utils.ShortHash(commit.Hash), commit.Name) return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
}) })
msg := utils.ResolvePlaceholderString( self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
self.Tr.Log.CherryPickCommits,
map[string]string{
"commitLines": strings.Join(commitLines, "\n"),
},
)
self.os.LogCommand(msg, false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: "HEAD", baseShaOrRoot: "HEAD",
instruction: daemon.NewCherryPickCommitsInstruction(commits), instruction: daemon.NewCherryPickCommitsInstruction(commits),
}).Run() }).Run()
} }
// CherryPickCommitsDuringRebase simply prepends the given commits to the existing git-rebase-todo file
func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Commit) error {
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) daemon.TodoLine {
return daemon.TodoLine{
Action: "pick",
Commit: commit,
}
})
todo := daemon.TodoLinesToString(todoLines)
filePath := filepath.Join(self.repoPaths.worktreeGitDirPath, "rebase-merge/git-rebase-todo")
return utils.PrependStrToTodoFile(filePath, []byte(todo))
}
// we can't start an interactive rebase from the first commit without passing the // we can't start an interactive rebase from the first commit without passing the
// '--root' arg // '--root' arg
func getBaseHashOrRoot(commits []*models.Commit, index int) string { func getBaseShaOrRoot(commits []*models.Commit, index int) string {
// We assume that the commits slice contains the initial commit of the repo. // We assume that the commits slice contains the initial commit of the repo.
// Technically this assumption could prove false, but it's unlikely you'll // Technically this assumption could prove false, but it's unlikely you'll
// be starting a rebase from 300 commits ago (which is the original commit limit // be starting a rebase from 300 commits ago (which is the original commit limit
// at time of writing) // at time of writing)
if index < len(commits) { if index < len(commits) {
return commits[index].Hash return commits[index].Sha
} else { } else {
return "--root" return "--root"
} }
+14 -11
View File
@@ -67,6 +67,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion}) instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
s.test(instance.RebaseBranch(s.arg)) s.test(instance.RebaseBranch(s.arg))
@@ -78,7 +79,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
// environment variables that suppress an interactive editor // environment variables that suppress an interactive editor
func TestRebaseSkipEditorCommand(t *testing.T) { func TestRebaseSkipEditorCommand(t *testing.T) {
cmdArgs := []string{"git", "blah"} cmdArgs := []string{"git", "blah"}
runner := oscommands.NewFakeRunner(t).ExpectFunc("matches editor env var", func(cmdObj oscommands.ICmdObj) bool { runner := oscommands.NewFakeRunner(t).ExpectFunc(func(cmdObj oscommands.ICmdObj) (string, error) {
assert.EqualValues(t, cmdArgs, cmdObj.Args()) assert.EqualValues(t, cmdArgs, cmdObj.Args())
envVars := cmdObj.GetEnvVars() envVars := cmdObj.GetEnvVars()
for _, regexStr := range []string{ for _, regexStr := range []string{
@@ -88,15 +89,16 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
`^GIT_SEQUENCE_EDITOR=.*$`, `^GIT_SEQUENCE_EDITOR=.*$`,
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$", "^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
} { } {
regexStr := regexStr
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool { foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
return regexp.MustCompile(regexStr).MatchString(envVar) return regexp.MustCompile(regexStr).MatchString(envVar)
}) })
if !foundMatch { if !foundMatch {
return false t.Errorf("expected environment variable %s to be set", regexStr)
} }
} }
return true return "", nil
}, "", nil) })
instance := buildRebaseCommands(commonDeps{runner: runner}) instance := buildRebaseCommands(commonDeps{runner: runner})
err := instance.runSkipEditorCommand(instance.cmd.New(cmdArgs)) err := instance.runSkipEditorCommand(instance.cmd.New(cmdArgs))
assert.NoError(t, err) assert.NoError(t, err)
@@ -109,7 +111,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
gitConfigMockResponses map[string]string gitConfigMockResponses map[string]string
commits []*models.Commit commits []*models.Commit
commitIndex int commitIndex int
fileName []string fileName string
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
test func(error) test func(error)
} }
@@ -120,7 +122,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
gitConfigMockResponses: nil, gitConfigMockResponses: nil,
commits: []*models.Commit{}, commits: []*models.Commit{},
commitIndex: 0, commitIndex: 0,
fileName: []string{"test999.txt"}, fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t), runner: oscommands.NewFakeRunner(t),
test: func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
@@ -129,9 +131,9 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
{ {
testName: "returns error when using gpg", testName: "returns error when using gpg",
gitConfigMockResponses: map[string]string{"commit.gpgsign": "true"}, gitConfigMockResponses: map[string]string{"commit.gpgsign": "true"},
commits: []*models.Commit{{Name: "commit", Hash: "123456"}}, commits: []*models.Commit{{Name: "commit", Sha: "123456"}},
commitIndex: 0, commitIndex: 0,
fileName: []string{"test999.txt"}, fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t), runner: oscommands.NewFakeRunner(t),
test: func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
@@ -141,11 +143,11 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
testName: "checks out file if it already existed", testName: "checks out file if it already existed",
gitConfigMockResponses: nil, gitConfigMockResponses: nil,
commits: []*models.Commit{ commits: []*models.Commit{
{Name: "commit", Hash: "123456"}, {Name: "commit", Sha: "123456"},
{Name: "commit2", Hash: "abcdef"}, {Name: "commit2", Sha: "abcdef"},
}, },
commitIndex: 0, commitIndex: 0,
fileName: []string{"test999.txt"}, fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil). ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil).
ExpectGitArgs([]string{"cat-file", "-e", "HEAD^:test999.txt"}, "", nil). ExpectGitArgs([]string{"cat-file", "-e", "HEAD^:test999.txt"}, "", nil).
@@ -161,6 +163,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{ instance := buildRebaseCommands(commonDeps{
runner: s.runner, runner: s.runner,
@@ -23,7 +23,7 @@ func NewReflogCommitLoader(common *common.Common, cmd oscommands.ICmdObjBuilder)
// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit // GetReflogCommits only returns the new reflog commits since the given lastReflogCommit
// if none is passed (i.e. it's value is nil) then we get all the reflog commits // if none is passed (i.e. it's value is nil) then we get all the reflog commits
func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string, filterAuthor string) ([]*models.Commit, bool, error) { func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string) ([]*models.Commit, bool, error) {
commits := make([]*models.Commit, 0) commits := make([]*models.Commit, 0)
cmdArgs := NewGitCmd("log"). cmdArgs := NewGitCmd("log").
@@ -31,24 +31,38 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
Arg("-g"). Arg("-g").
Arg("--abbrev=40"). Arg("--abbrev=40").
Arg("--format=%h%x00%ct%x00%gs%x00%p"). Arg("--format=%h%x00%ct%x00%gs%x00%p").
ArgIf(filterAuthor != "", "--author="+filterAuthor).
ArgIf(filterPath != "", "--follow", "--", filterPath). ArgIf(filterPath != "", "--follow", "--", filterPath).
ToArgv() ToArgv()
cmdObj := self.cmd.New(cmdArgs).DontLog() cmdObj := self.cmd.New(cmdArgs).DontLog()
onlyObtainedNewReflogCommits := false onlyObtainedNewReflogCommits := false
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit, ok := self.parseLine(line) fields := strings.SplitN(line, "\x00", 4)
if !ok { if len(fields) <= 3 {
return false, nil return false, nil
} }
unixTimestamp, _ := strconv.Atoi(fields[1])
parentHashes := fields[3]
parents := []string{}
if len(parentHashes) > 0 {
parents = strings.Split(parentHashes, " ")
}
commit := &models.Commit{
Sha: fields[0],
Name: fields[2],
UnixTimestamp: int64(unixTimestamp),
Status: models.StatusReflog,
Parents: parents,
}
// note that the unix timestamp here is the timestamp of the COMMIT, not the reflog entry itself, // note that the unix timestamp here is the timestamp of the COMMIT, not the reflog entry itself,
// so two consecutive reflog entries may have both the same hash and therefore same timestamp. // so two consecutive reflog entries may have both the same SHA and therefore same timestamp.
// We use the reflog message to disambiguate, and fingers crossed that we never see the same of those // We use the reflog message to disambiguate, and fingers crossed that we never see the same of those
// twice in a row. Reason being that it would mean we'd be erroneously exiting early. // twice in a row. Reason being that it would mean we'd be erroneously exiting early.
if lastReflogCommit != nil && self.sameReflogCommit(commit, lastReflogCommit) { if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp && commit.Name == lastReflogCommit.Name {
onlyObtainedNewReflogCommits = true onlyObtainedNewReflogCommits = true
// after this point we already have these reflogs loaded so we'll simply return the new ones // after this point we already have these reflogs loaded so we'll simply return the new ones
return true, nil return true, nil
@@ -63,30 +77,3 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
return commits, onlyObtainedNewReflogCommits, nil return commits, onlyObtainedNewReflogCommits, nil
} }
func (self *ReflogCommitLoader) sameReflogCommit(a *models.Commit, b *models.Commit) bool {
return a.Hash == b.Hash && a.UnixTimestamp == b.UnixTimestamp && a.Name == b.Name
}
func (self *ReflogCommitLoader) parseLine(line string) (*models.Commit, bool) {
fields := strings.SplitN(line, "\x00", 4)
if len(fields) <= 3 {
return nil, false
}
unixTimestamp, _ := strconv.Atoi(fields[1])
parentHashes := fields[3]
parents := []string{}
if len(parentHashes) > 0 {
parents = strings.Split(parentHashes, " ")
}
return &models.Commit{
Hash: fields[0],
Name: fields[2],
UnixTimestamp: int64(unixTimestamp),
Status: models.StatusReflog,
Parents: parents,
}, true
}
@@ -25,7 +25,6 @@ func TestGetReflogCommits(t *testing.T) {
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
lastReflogCommit *models.Commit lastReflogCommit *models.Commit
filterPath string filterPath string
filterAuthor string
expectedCommits []*models.Commit expectedCommits []*models.Commit
expectedOnlyObtainedNew bool expectedOnlyObtainedNew bool
expectedError error expectedError error
@@ -50,35 +49,35 @@ func TestGetReflogCommits(t *testing.T) {
lastReflogCommit: nil, lastReflogCommit: nil,
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B", Name: "checkout: moving from A to B",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"}, Parents: []string{"51baa8c1"},
}, },
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"}, Parents: []string{"51baa8c1"},
}, },
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B", Name: "checkout: moving from A to B",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"}, Parents: []string{"51baa8c1"},
}, },
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from master to A", Name: "checkout: moving from master to A",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"}, Parents: []string{"51baa8c1"},
}, },
{ {
Hash: "f4ddf2f0d4be4ccc7efa", Sha: "f4ddf2f0d4be4ccc7efa",
Name: "checkout: moving from A to master", Name: "checkout: moving from A to master",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643149435, UnixTimestamp: 1643149435,
@@ -94,7 +93,7 @@ func TestGetReflogCommits(t *testing.T) {
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil),
lastReflogCommit: &models.Commit{ lastReflogCommit: &models.Commit{
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
@@ -102,7 +101,7 @@ func TestGetReflogCommits(t *testing.T) {
}, },
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B", Name: "checkout: moving from A to B",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
@@ -118,7 +117,7 @@ func TestGetReflogCommits(t *testing.T) {
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--follow", "--", "path"}, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--follow", "--", "path"}, reflogOutput, nil),
lastReflogCommit: &models.Commit{ lastReflogCommit: &models.Commit{
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A", Name: "checkout: moving from B to A",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
@@ -127,32 +126,7 @@ func TestGetReflogCommits(t *testing.T) {
filterPath: "path", filterPath: "path",
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
Hash: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: models.StatusReflog,
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
},
expectedOnlyObtainedNew: true,
expectedError: nil,
},
{
testName: "when passing filterAuthor",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--author=John Doe <john@doe.com>"}, reflogOutput, nil),
lastReflogCommit: &models.Commit{
Hash: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: models.StatusReflog,
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
filterAuthor: "John Doe <john@doe.com>",
expectedCommits: []*models.Commit{
{
Hash: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B", Name: "checkout: moving from A to B",
Status: models.StatusReflog, Status: models.StatusReflog,
UnixTimestamp: 1643150483, UnixTimestamp: 1643150483,
@@ -176,13 +150,14 @@ func TestGetReflogCommits(t *testing.T) {
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) { t.Run(scenario.testName, func(t *testing.T) {
builder := &ReflogCommitLoader{ builder := &ReflogCommitLoader{
Common: utils.NewDummyCommon(), Common: utils.NewDummyCommon(),
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner), cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
} }
commits, onlyObtainednew, err := builder.GetReflogCommits(scenario.lastReflogCommit, scenario.filterPath, scenario.filterAuthor) commits, onlyObtainednew, err := builder.GetReflogCommits(scenario.lastReflogCommit, scenario.filterPath)
assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew) assert.Equal(t, scenario.expectedOnlyObtainedNew, onlyObtainednew)
assert.Equal(t, scenario.expectedError, err) assert.Equal(t, scenario.expectedError, err)
t.Logf("actual commits: \n%s", litter.Sdump(commits)) t.Logf("actual commits: \n%s", litter.Sdump(commits))
+1 -21
View File
@@ -2,7 +2,6 @@ package git_commands
import ( import (
"fmt" "fmt"
"strings"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
) )
@@ -54,15 +53,7 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
Arg(remoteName, "--delete", branchName). Arg(remoteName, "--delete", branchName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
}
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
cmdArgs := NewGitCmd("push").
Arg(remoteName, "--delete", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
} }
// CheckRemoteBranchExists Returns remote branch // CheckRemoteBranchExists Returns remote branch
@@ -75,14 +66,3 @@ func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
return err == nil return err == nil
} }
// Resolve what might be a aliased URL into a full URL
// SEE: `man -P 'less +/--get-url +n' git-ls-remote`
func (self *RemoteCommands) GetRemoteURL(remoteName string) (string, error) {
cmdArgs := NewGitCmd("ls-remote").
Arg("--get-url", remoteName).
ToArgv()
url, err := self.cmd.New(cmdArgs).RunWithOutput()
return strings.TrimSpace(url), err
}
+18 -70
View File
@@ -2,16 +2,14 @@ package git_commands
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"sync"
"github.com/jesseduffield/generics/slices"
gogit "github.com/jesseduffield/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"golang.org/x/exp/slices"
) )
type RemoteLoader struct { type RemoteLoader struct {
@@ -33,31 +31,29 @@ func NewRemoteLoader(
} }
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) { func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
wg := sync.WaitGroup{} cmdArgs := NewGitCmd("branch").Arg("-r").ToArgv()
wg.Add(1) remoteBranchesStr, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
var remoteBranchesByRemoteName map[string][]*models.RemoteBranch return nil, err
var remoteBranchesErr error }
go utils.Safe(func() {
defer wg.Done()
remoteBranchesByRemoteName, remoteBranchesErr = self.getRemoteBranchesByRemoteName()
})
goGitRemotes, err := self.getGoGitRemotes() goGitRemotes, err := self.getGoGitRemotes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
wg.Wait() // first step is to get our remotes from go-git
remotes := slices.Map(goGitRemotes, func(goGitRemote *gogit.Remote) *models.Remote {
if remoteBranchesErr != nil {
return nil, remoteBranchesErr
}
remotes := lo.Map(goGitRemotes, func(goGitRemote *gogit.Remote, _ int) *models.Remote {
remoteName := goGitRemote.Config().Name remoteName := goGitRemote.Config().Name
branches := remoteBranchesByRemoteName[remoteName]
re := regexp.MustCompile(fmt.Sprintf(`(?m)^\s*%s\/([\S]+)`, regexp.QuoteMeta(remoteName)))
matches := re.FindAllStringSubmatch(remoteBranchesStr, -1)
branches := slices.Map(matches, func(match []string) *models.RemoteBranch {
return &models.RemoteBranch{
Name: match[1],
RemoteName: remoteName,
}
})
return &models.Remote{ return &models.Remote{
Name: goGitRemote.Config().Name, Name: goGitRemote.Config().Name,
@@ -80,51 +76,3 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
return remotes, nil return remotes, nil
} }
func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models.RemoteBranch, error) {
remoteBranchesByRemoteName := make(map[string][]*models.RemoteBranch)
var sortOrder string
switch strings.ToLower(self.AppState.RemoteBranchSortOrder) {
case "alphabetical":
sortOrder = "refname"
case "date":
sortOrder = "-committerdate"
default:
sortOrder = "refname"
}
cmdArgs := NewGitCmd("for-each-ref").
Arg(fmt.Sprintf("--sort=%s", sortOrder)).
Arg("--format=%(refname:short)").
Arg("refs/remotes").
ToArgv()
err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
line = strings.TrimSpace(line)
split := strings.SplitN(line, "/", 2)
if len(split) != 2 {
return false, nil
}
remoteName := split[0]
name := split[1]
_, ok := remoteBranchesByRemoteName[remoteName]
if !ok {
remoteBranchesByRemoteName[remoteName] = []*models.RemoteBranch{}
}
remoteBranchesByRemoteName[remoteName] = append(remoteBranchesByRemoteName[remoteName],
&models.RemoteBranch{
Name: name,
RemoteName: remoteName,
})
return false, nil
})
if err != nil {
return nil, err
}
return remoteBranchesByRemoteName, nil
}
-175
View File
@@ -1,175 +0,0 @@
package git_commands
import (
ioFs "io/fs"
"path"
"path/filepath"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spf13/afero"
)
type RepoPaths struct {
worktreePath string
worktreeGitDirPath string
repoPath string
repoGitDirPath string
repoName string
}
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
// Path to the current worktree. If we're in the main worktree, this will
// be the same as RepoPath()
func (self *RepoPaths) WorktreePath() string {
return self.worktreePath
}
// Path of the worktree's git dir.
// If we're in the main worktree, this will be the .git dir under the RepoPath().
// If we're in a linked worktree, it will be the directory pointed at by the worktree's .git file
func (self *RepoPaths) WorktreeGitDirPath() string {
return self.worktreeGitDirPath
}
// Path of the repo. If we're in a the main worktree, this will be the same as WorktreePath()
// If we're in a bare repo, it will be the parent folder of the bare repo
func (self *RepoPaths) RepoPath() string {
return self.repoPath
}
// path of the git-dir for the repo.
// If this is a bare repo, it will be the location of the bare repo
// If this is a non-bare repo, it will be the location of the .git dir in
// the main worktree.
func (self *RepoPaths) RepoGitDirPath() string {
return self.repoGitDirPath
}
// Name of the repo. Basename of the folder containing the repo.
func (self *RepoPaths) RepoName() string {
return self.repoName
}
// Returns the repo paths for a typical repo
func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{
worktreePath: currentPath,
worktreeGitDirPath: path.Join(currentPath, ".git"),
repoPath: currentPath,
repoGitDirPath: path.Join(currentPath, ".git"),
repoName: "lazygit",
}
}
func GetRepoPaths(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
) (*RepoPaths, error) {
gitDirOutput, err := callGitRevParse(cmd, version, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
if err != nil {
return nil, err
}
gitDirResults := strings.Split(utils.NormalizeLinefeeds(gitDirOutput), "\n")
worktreePath := gitDirResults[0]
worktreeGitDirPath := gitDirResults[1]
repoGitDirPath := gitDirResults[2]
if version.IsOlderThanVersion(&gitPathFormatVersion) {
repoGitDirPath, err = filepath.Abs(repoGitDirPath)
if err != nil {
return nil, err
}
}
// If we're in a submodule, --show-superproject-working-tree will return
// a value, meaning gitDirResults will be length 4. In that case
// return the worktree path as the repoPath. Otherwise we're in a
// normal repo or a worktree so return the parent of the git common
// dir (repoGitDirPath)
isSubmodule := len(gitDirResults) == 4
var repoPath string
if isSubmodule {
repoPath = worktreePath
} else {
repoPath = path.Dir(repoGitDirPath)
}
repoName := path.Base(repoPath)
return &RepoPaths{
worktreePath: worktreePath,
worktreeGitDirPath: worktreeGitDirPath,
repoPath: repoPath,
repoGitDirPath: repoGitDirPath,
repoName: repoName,
}, nil
}
func callGitRevParse(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
gitRevArgs ...string,
) (string, error) {
return callGitRevParseWithDir(cmd, version, "", gitRevArgs...)
}
func callGitRevParseWithDir(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
dir string,
gitRevArgs ...string,
) (string, error) {
gitRevParse := NewGitCmd("rev-parse").ArgIf(version.IsAtLeastVersion(&gitPathFormatVersion), "--path-format=absolute").Arg(gitRevArgs...)
if dir != "" {
gitRevParse.Dir(dir)
}
gitCmd := cmd.New(gitRevParse.ToArgv()).DontLog()
res, err := gitCmd.RunWithOutput()
if err != nil {
return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err)
}
return strings.TrimSpace(res), nil
}
// Returns the paths of linked worktrees
func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := path.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := fs.Stat(worktreeGitDirsPath)
if err != nil {
return result
}
_ = afero.Walk(fs, worktreeGitDirsPath, func(currPath string, info ioFs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
gitDirPath := path.Join(currPath, "gitdir")
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
if err != nil {
// ignoring error
return nil
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := path.Dir(trimmedGitDir)
result = append(result, worktreeDir)
return nil
})
return result
}
@@ -1,136 +0,0 @@
package git_commands
import (
"fmt"
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
type (
argFn func() []string
errFn func(getRevParseArgs argFn) error
)
type Scenario struct {
Name string
BeforeFunc func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn)
Path string
Expected *RepoPaths
Err errFn
}
func TestGetRepoPaths(t *testing.T) {
scenarios := []Scenario{
{
Name: "typical case",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
expectedOutput := []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
"/path/to/repo/.git",
// --git-common-dir
"/path/to/repo/.git",
// --show-superproject-working-tree
}
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/repo/.git",
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
},
Err: nil,
},
{
Name: "submodule",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
expectedOutput := []string{
// --show-toplevel
"/path/to/repo/submodule1",
// --git-dir
"/path/to/repo/.git/modules/submodule1",
// --git-common-dir
"/path/to/repo/.git/modules/submodule1",
// --show-superproject-working-tree
"/path/to/repo",
}
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
nil)
},
Path: "/path/to/repo/submodule1",
Expected: &RepoPaths{
worktreePath: "/path/to/repo/submodule1",
worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1",
},
Err: nil,
},
{
Name: "git rev-parse returns an error",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
"",
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
},
Path: "/path/to/repo/worktree2",
Expected: nil,
Err: func(getRevParseArgs argFn) error {
args := strings.Join(getRevParseArgs(), " ")
return errors.New(
fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
)
},
},
}
for _, s := range scenarios {
t.Run(s.Name, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t)
cmd := oscommands.NewDummyCmdObjBuilder(runner)
version, err := GetGitVersion(oscommands.NewDummyOSCommand())
if err != nil {
t.Fatal(err)
}
getRevParseArgs := func() []string {
args := []string{"rev-parse"}
if version.IsAtLeast(2, 31, 0) {
args = append(args, "--path-format=absolute")
}
return args
}
// prepare the filesystem for the scenario
s.BeforeFunc(runner, getRevParseArgs)
repoPaths, err := GetRepoPaths(cmd, version)
// check the error and the paths
if s.Err != nil {
scenarioErr := s.Err(getRevParseArgs)
assert.Error(t, err)
assert.EqualError(t, err, scenarioErr.Error())
} else {
assert.Nil(t, err)
assert.Equal(t, s.Expected, repoPaths)
}
})
}
}
+12 -26
View File
@@ -60,35 +60,34 @@ func (self *StashCommands) Push(message string) error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Store(hash string, message string) error { func (self *StashCommands) Store(sha string, message string) error {
trimmedMessage := strings.Trim(message, " \t") trimmedMessage := strings.Trim(message, " \t")
cmdArgs := NewGitCmd("stash").Arg("store"). cmdArgs := NewGitCmd("stash").Arg("store").
ArgIf(trimmedMessage != "", "-m", trimmedMessage). ArgIf(trimmedMessage != "", "-m", trimmedMessage).
Arg(hash). Arg(sha).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Hash(index int) (string, error) { func (self *StashCommands) Sha(index int) (string, error) {
cmdArgs := NewGitCmd("rev-parse"). cmdArgs := NewGitCmd("rev-parse").
Arg(fmt.Sprintf("refs/stash@{%d}", index)). Arg(fmt.Sprintf("refs/stash@{%d}", index)).
ToArgv() ToArgv()
hash, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs() sha, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
return strings.Trim(hash, "\r\n"), err return strings.Trim(sha, "\r\n"), err
} }
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj { func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show"). cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p"). Arg("-p").
Arg("--stat"). Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)). Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)). Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("stash@{%d}", index)). Arg(fmt.Sprintf("stash@{%d}", index)).
Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
@@ -121,22 +120,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
return nil return nil
} }
// SaveStagedChanges stashes only the currently staged changes. // SaveStagedChanges stashes only the currently staged changes. This takes a few steps
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (self *StashCommands) SaveStagedChanges(message string) error { func (self *StashCommands) SaveStagedChanges(message string) error {
if self.version.IsAtLeast(2, 35, 0) {
return self.cmd.New(NewGitCmd("stash").Arg("push").Arg("--staged").Arg("-m", message).ToArgv()).Run()
}
// Git versions older than 2.35.0 don't support the --staged flag, so we
// need to fall back to a more complex solution.
// Shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
//
// Note that this method has a few bugs:
// - it fails when there are *only* staged changes
// - it fails when staged and unstaged changes within a single file are too close together
// We don't bother fixing these, because users can simply update git when
// they are affected by these issues.
// wrap in 'writing', which uses a mutex // wrap in 'writing', which uses a mutex
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("stash").Arg("--keep-index").ToArgv(), NewGitCmd("stash").Arg("--keep-index").ToArgv(),
@@ -192,7 +178,7 @@ func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
} }
func (self *StashCommands) Rename(index int, message string) error { func (self *StashCommands) Rename(index int, message string) error {
hash, err := self.Hash(index) sha, err := self.Sha(index)
if err != nil { if err != nil {
return err return err
} }
@@ -201,7 +187,7 @@ func (self *StashCommands) Rename(index int, message string) error {
return err return err
} }
err = self.Store(hash, message) err = self.Store(sha, message)
if err != nil { if err != nil {
return err return err
} }
+6 -21
View File
@@ -5,11 +5,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
) )
type StashLoader struct { type StashLoader struct {
@@ -32,14 +32,14 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries() return self.getUnfilteredStashEntries()
} }
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--name-only", "--pretty=%ct|%gs").ToArgv() cmdArgs := NewGitCmd("stash").Arg("list", "--name-only").ToArgv()
rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return self.getUnfilteredStashEntries() return self.getUnfilteredStashEntries()
} }
stashEntries := []*models.StashEntry{} stashEntries := []*models.StashEntry{}
var currentStashEntry *models.StashEntry var currentStashEntry *models.StashEntry
lines := utils.SplitNul(rawString) lines := utils.SplitLines(rawString)
isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") } isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") }
re := regexp.MustCompile(`stash@\{(\d+)\}`) re := regexp.MustCompile(`stash@\{(\d+)\}`)
@@ -66,32 +66,17 @@ outer:
} }
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%ct|%gs").ToArgv() cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput() rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry { return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index) return self.stashEntryFromLine(line, index)
}) })
} }
func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry { func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry {
model := &models.StashEntry{ return &models.StashEntry{
Name: line, Name: line,
Index: index, Index: index,
} }
tstr, msg, ok := strings.Cut(line, "|")
if !ok {
return model
}
t, err := strconv.ParseInt(tstr, 10, 64)
if err != nil {
return model
}
model.Name = msg
model.Recency = utils.UnixToTimeAgo(t)
return model
} }
@@ -22,14 +22,14 @@ func TestGetStashEntries(t *testing.T) {
"No stash entries found", "No stash entries found",
"", "",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, "", nil), ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"}, "", nil),
[]*models.StashEntry{}, []*models.StashEntry{},
}, },
{ {
"Several stash entries found", "Several stash entries found",
"", "",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"},
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
nil, nil,
), ),
@@ -47,6 +47,7 @@ func TestGetStashEntries(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
cmd := oscommands.NewDummyCmdObjBuilder(s.runner) cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
+24 -26
View File
@@ -47,7 +47,7 @@ func TestStashSave(t *testing.T) {
func TestStashStore(t *testing.T) { func TestStashStore(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
hash string sha string
message string message string
expected []string expected []string
} }
@@ -55,44 +55,45 @@ func TestStashStore(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "Non-empty message", testName: "Non-empty message",
hash: "0123456789abcdef", sha: "0123456789abcdef",
message: "New stash name", message: "New stash name",
expected: []string{"stash", "store", "-m", "New stash name", "0123456789abcdef"}, expected: []string{"stash", "store", "-m", "New stash name", "0123456789abcdef"},
}, },
{ {
testName: "Empty message", testName: "Empty message",
hash: "0123456789abcdef", sha: "0123456789abcdef",
message: "", message: "",
expected: []string{"stash", "store", "0123456789abcdef"}, expected: []string{"stash", "store", "0123456789abcdef"},
}, },
{ {
testName: "Space message", testName: "Space message",
hash: "0123456789abcdef", sha: "0123456789abcdef",
message: " ", message: " ",
expected: []string{"stash", "store", "0123456789abcdef"}, expected: []string{"stash", "store", "0123456789abcdef"},
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expected, "", nil) ExpectGitArgs(s.expected, "", nil)
instance := buildStashCommands(commonDeps{runner: runner}) instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Store(s.hash, s.message)) assert.NoError(t, instance.Store(s.sha, s.message))
runner.CheckForMissingCalls() runner.CheckForMissingCalls()
}) })
} }
} }
func TestStashHash(t *testing.T) { func TestStashSha(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil) ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil)
instance := buildStashCommands(commonDeps{runner: runner}) instance := buildStashCommands(commonDeps{runner: runner})
hash, err := instance.Hash(5) sha, err := instance.Sha(5)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", hash) assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", sha)
runner.CheckForMissingCalls() runner.CheckForMissingCalls()
} }
@@ -111,36 +112,32 @@ func TestStashStashEntryCmdObj(t *testing.T) {
index: 5, index: 5,
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "stash@{5}"}, expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "stash@{5}"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
index: 5, index: 5,
contextSize: 77, contextSize: 77,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "stash@{5}"}, expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "stash@{5}"},
}, },
{ {
testName: "Default case", testName: "Default case",
index: 5, index: 5,
contextSize: 3, contextSize: 3,
ignoreWhitespace: true, ignoreWhitespace: true,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "stash@{5}"}, expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "stash@{5}"},
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
appState := &config.AppState{} userConfig.Git.DiffContextSize = s.contextSize
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace instance := buildStashCommands(commonDeps{userConfig: userConfig})
appState.DiffContextSize = s.contextSize
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
instance := buildStashCommands(commonDeps{userConfig: userConfig, appState: appState, repoPaths: &repoPaths})
cmdStr := instance.ShowStashEntryCmdObj(s.index).Args() cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).Args()
assert.Equal(t, s.expected, cmdStr) assert.Equal(t, s.expected, cmdStr)
}) })
} }
@@ -151,8 +148,8 @@ func TestStashRename(t *testing.T) {
testName string testName string
index int index int
message string message string
expectedHashCmd []string expectedShaCmd []string
hashResult string shaResult string
expectedDropCmd []string expectedDropCmd []string
expectedStoreCmd []string expectedStoreCmd []string
} }
@@ -162,8 +159,8 @@ func TestStashRename(t *testing.T) {
testName: "Default case", testName: "Default case",
index: 3, index: 3,
message: "New message", message: "New message",
expectedHashCmd: []string{"rev-parse", "refs/stash@{3}"}, expectedShaCmd: []string{"rev-parse", "refs/stash@{3}"},
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n", shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{3}"}, expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"}, expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
}, },
@@ -171,17 +168,18 @@ func TestStashRename(t *testing.T) {
testName: "Empty message", testName: "Empty message",
index: 4, index: 4,
message: "", message: "",
expectedHashCmd: []string{"rev-parse", "refs/stash@{4}"}, expectedShaCmd: []string{"rev-parse", "refs/stash@{4}"},
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n", shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{4}"}, expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"}, expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
ExpectGitArgs(s.expectedHashCmd, s.hashResult, nil). ExpectGitArgs(s.expectedShaCmd, s.shaResult, nil).
ExpectGitArgs(s.expectedDropCmd, "", nil). ExpectGitArgs(s.expectedDropCmd, "", nil).
ExpectGitArgs(s.expectedStoreCmd, "", nil) ExpectGitArgs(s.expectedStoreCmd, "", nil)
instance := buildStashCommands(commonDeps{runner: runner}) instance := buildStashCommands(commonDeps{runner: runner})
+10 -27
View File
@@ -1,7 +1,6 @@
package git_commands package git_commands
import ( import (
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@@ -25,16 +24,19 @@ func NewStatusCommands(
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase // RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase // and "interactive" for interactive rebase
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) { func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
ok, err := self.IsInNormalRebase() exists, err := self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-apply"))
if err == nil && ok { if err != nil {
return enums.REBASE_MODE_NONE, err
}
if exists {
return enums.REBASE_MODE_NORMAL, nil return enums.REBASE_MODE_NORMAL, nil
} }
ok, err = self.IsInInteractiveRebase() exists, err = self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-merge"))
if err == nil && ok { if exists {
return enums.REBASE_MODE_INTERACTIVE, err return enums.REBASE_MODE_INTERACTIVE, err
} else {
return enums.REBASE_MODE_NONE, err
} }
return enums.REBASE_MODE_NONE, err
} }
func (self *StatusCommands) WorkingTreeState() enums.RebaseMode { func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
@@ -65,26 +67,7 @@ func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
return strconv.ParseBool(strings.TrimSpace(res)) return strconv.ParseBool(strings.TrimSpace(res))
} }
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
}
func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
}
// IsInMergeState states whether we are still mid-merge // IsInMergeState states whether we are still mid-merge
func (self *StatusCommands) IsInMergeState() (bool, error) { func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD")) return self.os.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD"))
}
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
// being rebased, or empty string when we're not in a rebase
func (self *StatusCommands) BranchBeingRebased() string {
for _, dir := range []string{"rebase-merge", "rebase-apply"} {
if bytesContent, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), dir, "head-name")); err == nil {
return strings.TrimSpace(string(bytesContent))
}
}
return ""
} }
+14 -59
View File
@@ -26,12 +26,8 @@ func NewSubmoduleCommands(gitCommon *GitCommon) *SubmoduleCommands {
} }
} }
func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig) ([]*models.SubmoduleConfig, error) { func (self *SubmoduleCommands) GetConfigs() ([]*models.SubmoduleConfig, error) {
gitModulesPath := ".gitmodules" file, err := os.Open(".gitmodules")
if parentModule != nil {
gitModulesPath = filepath.Join(parentModule.FullPath(), gitModulesPath)
}
file, err := os.Open(gitModulesPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, nil return nil, nil
@@ -55,27 +51,21 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
} }
configs := []*models.SubmoduleConfig{} configs := []*models.SubmoduleConfig{}
lastConfigIdx := -1
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok { if name, ok := firstMatch(line, `\[submodule "(.*)"\]`); ok {
configs = append(configs, &models.SubmoduleConfig{ configs = append(configs, &models.SubmoduleConfig{Name: name})
Name: name, ParentModule: parentModule,
})
lastConfigIdx = len(configs) - 1
continue continue
} }
if lastConfigIdx != -1 { if len(configs) > 0 {
lastConfig := configs[len(configs)-1]
if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok { if path, ok := firstMatch(line, `\s*path\s*=\s*(.*)\s*`); ok {
configs[lastConfigIdx].Path = path lastConfig.Path = path
nestedConfigs, err := self.GetConfigs(configs[lastConfigIdx])
if err == nil {
configs = append(configs, nestedConfigs...)
}
} else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok { } else if url, ok := firstMatch(line, `\s*url\s*=\s*(.*)\s*`); ok {
configs[lastConfigIdx].Url = url lastConfig.Url = url
} }
} }
} }
@@ -87,12 +77,12 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
// if the path does not exist then it hasn't yet been initialized so we'll swallow the error // if the path does not exist then it hasn't yet been initialized so we'll swallow the error
// because the intention here is to have no dirty worktree state // because the intention here is to have no dirty worktree state
if _, err := os.Stat(submodule.Path); os.IsNotExist(err) { if _, err := os.Stat(submodule.Path); os.IsNotExist(err) {
self.Log.Infof("submodule path %s does not exist, returning", submodule.FullPath()) self.Log.Infof("submodule path %s does not exist, returning", submodule.Path)
return nil return nil
} }
cmdArgs := NewGitCmd("stash"). cmdArgs := NewGitCmd("stash").
Dir(submodule.FullPath()). RepoPath(submodule.Path).
Arg("--include-untracked"). Arg("--include-untracked").
ToArgv() ToArgv()
@@ -100,13 +90,8 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
} }
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
parentDir := ""
if submodule.ParentModule != nil {
parentDir = submodule.ParentModule.FullPath()
}
cmdArgs := NewGitCmd("submodule"). cmdArgs := NewGitCmd("submodule").
Arg("update", "--init", "--force", "--", submodule.Path). Arg("update", "--init", "--force", "--", submodule.Path).
DirIf(parentDir != "", parentDir).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
@@ -122,20 +107,6 @@ func (self *SubmoduleCommands) UpdateAll() error {
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677 // based on https://gist.github.com/myusuf3/7f645819ded92bda6677
if submodule.ParentModule != nil {
wd, err := os.Getwd()
if err != nil {
return err
}
err = os.Chdir(submodule.ParentModule.FullPath())
if err != nil {
return err
}
defer func() { _ = os.Chdir(wd) }()
}
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("submodule"). NewGitCmd("submodule").
Arg("deinit", "--force", "--", submodule.Path).ToArgv(), Arg("deinit", "--force", "--", submodule.Path).ToArgv(),
@@ -168,9 +139,7 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
self.Log.Error(err) self.Log.Error(err)
} }
// We may in fact want to use the repo's git dir path but git docs say not to return os.RemoveAll(filepath.Join(self.dotGitDir, "modules", submodule.Path))
// mix submodules and worktrees anyway.
return os.RemoveAll(submodule.GitDirPath(self.repoPaths.repoGitDirPath))
} }
func (self *SubmoduleCommands) Add(name string, path string, url string) error { func (self *SubmoduleCommands) Add(name string, path string, url string) error {
@@ -187,24 +156,10 @@ func (self *SubmoduleCommands) Add(name string, path string, url string) error {
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newUrl string) error { func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error {
if submodule.ParentModule != nil {
wd, err := os.Getwd()
if err != nil {
return err
}
err = os.Chdir(submodule.ParentModule.FullPath())
if err != nil {
return err
}
defer func() { _ = os.Chdir(wd) }()
}
setUrlCmdStr := NewGitCmd("config"). setUrlCmdStr := NewGitCmd("config").
Arg( Arg(
"--file", ".gitmodules", "submodule."+submodule.Name+".url", newUrl, "--file", ".gitmodules", "submodule."+name+".url", newUrl,
). ).
ToArgv() ToArgv()
@@ -213,7 +168,7 @@ func (self *SubmoduleCommands) UpdateUrl(submodule *models.SubmoduleConfig, newU
return err return err
} }
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", submodule.Path). syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", path).
ToArgv() ToArgv()
if err := self.cmd.New(syncCmdStr).Run(); err != nil { if err := self.cmd.New(syncCmdStr).Run(); err != nil {
+16 -28
View File
@@ -19,7 +19,6 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
// Push pushes to a branch // Push pushes to a branch
type PushOpts struct { type PushOpts struct {
Force bool Force bool
ForceWithLease bool
UpstreamRemote string UpstreamRemote string
UpstreamBranch string UpstreamBranch string
SetUpstream bool SetUpstream bool
@@ -31,14 +30,13 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
} }
cmdArgs := NewGitCmd("push"). cmdArgs := NewGitCmd("push").
ArgIf(opts.Force, "--force"). ArgIf(opts.Force, "--force-with-lease").
ArgIf(opts.ForceWithLease, "--force-with-lease").
ArgIf(opts.SetUpstream, "--set-upstream"). ArgIf(opts.SetUpstream, "--set-upstream").
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote). ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
ArgIf(opts.UpstreamBranch != "", "HEAD:"+opts.UpstreamBranch). ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv() ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task) cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
return cmdObj, nil return cmdObj, nil
} }
@@ -51,16 +49,10 @@ func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error {
return cmdObj.Run() return cmdObj.Run()
} }
func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder {
return NewGitCmd("fetch").
ArgIf(fetchAll, "--all").
// avoid writing to .git/FETCH_HEAD; this allows running a pull
// concurrently without getting errors
ArgIf(self.version.IsAtLeast(2, 29, 0), "--no-write-fetch-head")
}
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj { func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv() cmdArgs := NewGitCmd("fetch").
ArgIf(self.UserConfig.Git.FetchAll, "--all").
ToArgv()
cmdObj := self.cmd.New(cmdArgs) cmdObj := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task) cmdObj.PromptOnCredentialRequest(task)
@@ -72,10 +64,13 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
} }
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj { func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv() cmdArgs := NewGitCmd("fetch").
ArgIf(self.UserConfig.Git.FetchAll, "--all").
ToArgv()
cmdObj := self.cmd.New(cmdArgs) cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest() cmdObj.DontLog().FailOnCredentialRequest()
cmdObj.WithMutex(self.syncMutex)
return cmdObj return cmdObj
} }
@@ -87,7 +82,6 @@ type PullOptions struct {
RemoteName string RemoteName string
BranchName string BranchName string
FastForwardOnly bool FastForwardOnly bool
WorktreeGitDir string
} }
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error { func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
@@ -96,32 +90,26 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
ArgIf(opts.FastForwardOnly, "--ff-only"). ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.RemoteName != "", opts.RemoteName). ArgIf(opts.RemoteName != "", opts.RemoteName).
ArgIf(opts.BranchName != "", opts.BranchName). ArgIf(opts.BranchName != "", opts.BranchName).
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
ToArgv() ToArgv()
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured. // has 'pull.rebase = interactive' configured.
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run() return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FastForward( func (self *SyncCommands) FastForward(task gocui.Task, branchName string, remoteName string, remoteBranchName string) error {
task gocui.Task, cmdArgs := NewGitCmd("fetch").
branchName string,
remoteName string,
remoteBranchName string,
) error {
cmdArgs := self.fetchCommandBuilder(false).
Arg(remoteName). Arg(remoteName).
Arg(remoteBranchName + ":" + branchName). Arg(remoteBranchName + ":" + branchName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error { func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
cmdArgs := self.fetchCommandBuilder(false). cmdArgs := NewGitCmd("fetch").
Arg(remoteName). Arg(remoteName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
+13 -18
View File
@@ -18,70 +18,62 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "Push with force disabled", testName: "Push with force disabled",
opts: PushOpts{ForceWithLease: false}, opts: PushOpts{Force: false},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push"}) assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{
testName: "Push with force-with-lease enabled",
opts: PushOpts{ForceWithLease: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
assert.NoError(t, err)
},
},
{ {
testName: "Push with force enabled", testName: "Push with force enabled",
opts: PushOpts{Force: true}, opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force"}) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
testName: "Push with force disabled, upstream supplied", testName: "Push with force disabled, upstream supplied",
opts: PushOpts{ opts: PushOpts{
ForceWithLease: false, Force: false,
UpstreamRemote: "origin", UpstreamRemote: "origin",
UpstreamBranch: "master", UpstreamBranch: "master",
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "HEAD:master"}) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
testName: "Push with force disabled, setting upstream", testName: "Push with force disabled, setting upstream",
opts: PushOpts{ opts: PushOpts{
ForceWithLease: false, Force: false,
UpstreamRemote: "origin", UpstreamRemote: "origin",
UpstreamBranch: "master", UpstreamBranch: "master",
SetUpstream: true, SetUpstream: true,
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "HEAD:master"}) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
testName: "Push with force-with-lease enabled, setting upstream", testName: "Push with force enabled, setting upstream",
opts: PushOpts{ opts: PushOpts{
ForceWithLease: true, Force: true,
UpstreamRemote: "origin", UpstreamRemote: "origin",
UpstreamBranch: "master", UpstreamBranch: "master",
SetUpstream: true, SetUpstream: true,
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "HEAD:master"}) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
testName: "Push with remote branch but no origin", testName: "Push with remote branch but no origin",
opts: PushOpts{ opts: PushOpts{
ForceWithLease: true, Force: true,
UpstreamRemote: "", UpstreamRemote: "",
UpstreamBranch: "master", UpstreamBranch: "master",
SetUpstream: true, SetUpstream: true,
@@ -94,6 +86,7 @@ func TestSyncPush(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{}) instance := buildSyncCommands(commonDeps{})
task := gocui.NewFakeTask() task := gocui.NewFakeTask()
@@ -131,6 +124,7 @@ func TestSyncFetch(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{}) instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig instance.UserConfig.Git.FetchAll = s.fetchAllConfig
@@ -169,6 +163,7 @@ func TestSyncFetchBackground(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{}) instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig instance.UserConfig.Git.FetchAll = s.fetchAllConfig
+5 -17
View File
@@ -12,19 +12,16 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
} }
} }
func (self *TagCommands) CreateLightweight(tagName string, ref string, force bool) error { func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
cmdArgs := NewGitCmd("tag"). cmdArgs := NewGitCmd("tag").Arg("--", tagName).
ArgIf(force, "--force").
Arg("--", tagName).
ArgIf(len(ref) > 0, ref). ArgIf(len(ref) > 0, ref).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) error { func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
cmdArgs := NewGitCmd("tag").Arg(tagName). cmdArgs := NewGitCmd("tag").Arg(tagName).
ArgIf(force, "--force").
ArgIf(len(ref) > 0, ref). ArgIf(len(ref) > 0, ref).
Arg("-m", msg). Arg("-m", msg).
ToArgv() ToArgv()
@@ -32,16 +29,7 @@ func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) e
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) HasTag(tagName string) bool { func (self *TagCommands) Delete(tagName string) error {
cmdArgs := NewGitCmd("show-ref").
Arg("--tags", "--quiet", "--verify", "--").
Arg("refs/tags/" + tagName).
ToArgv()
return self.cmd.New(cmdArgs).Run() == nil
}
func (self *TagCommands) LocalDelete(tagName string) error {
cmdArgs := NewGitCmd("tag").Arg("-d", tagName). cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
ToArgv() ToArgv()
@@ -52,5 +40,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName). cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
} }
+2 -2
View File
@@ -3,11 +3,11 @@ package git_commands
import ( import (
"regexp" "regexp"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
) )
type TagLoader struct { type TagLoader struct {
@@ -38,7 +38,7 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`) lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
tags := lo.Map(split, func(line string, _ int) *models.Tag { tags := slices.Map(split, func(line string) *models.Tag {
matches := lineRegex.FindStringSubmatch(line) matches := lineRegex.FindStringSubmatch(line)
tagName := matches[1] tagName := matches[1]
message := "" message := ""
@@ -44,6 +44,7 @@ func TestGetTags(t *testing.T) {
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.testName, func(t *testing.T) { t.Run(scenario.testName, func(t *testing.T) {
loader := &TagLoader{ loader := &TagLoader{
Common: utils.NewDummyCommon(), Common: utils.NewDummyCommon(),
-8
View File
@@ -69,11 +69,3 @@ func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool { func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool {
return v.IsOlderThan(version.Major, version.Minor, version.Patch) return v.IsOlderThan(version.Major, version.Minor, version.Patch)
} }
func (v *GitVersion) IsAtLeast(major, minor, patch int) bool {
return !v.IsOlderThan(major, minor, patch)
}
func (v *GitVersion) IsAtLeastVersion(version *GitVersion) bool {
return v.IsAtLeast(version.Major, version.Minor, version.Patch)
}
@@ -45,12 +45,3 @@ func TestGitVersionIsOlderThan(t *testing.T) {
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0)) assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(2, 1, 0))
assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0)) assert.True(t, (&GitVersion{2, 0, 1, ""}).IsOlderThan(3, 0, 0))
} }
func TestGitVersionIsAtLeast(t *testing.T) {
assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(1, 99, 99))
assert.True(t, (&GitVersion{2, 0, 0, ""}).IsAtLeast(2, 0, 0))
assert.True(t, (&GitVersion{2, 1, 0, ""}).IsAtLeast(2, 0, 9))
assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(2, 1, 0))
assert.False(t, (&GitVersion{2, 0, 1, ""}).IsAtLeast(3, 0, 0))
}
+46 -62
View File
@@ -3,7 +3,6 @@ package git_commands
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -32,6 +31,10 @@ func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("mergetool").ToArgv()) return self.cmd.New(NewGitCmd("mergetool").ToArgv())
} }
func (self *WorkingTreeCommands) OpenMergeTool() error {
return self.OpenMergeToolCmdObj().Run()
}
// StageFile stages a file // StageFile stages a file
func (self *WorkingTreeCommands) StageFile(path string) error { func (self *WorkingTreeCommands) StageFile(path string) error {
return self.StageFiles([]string{path}) return self.StageFiles([]string{path})
@@ -58,20 +61,21 @@ func (self *WorkingTreeCommands) UnstageAll() error {
// UnStageFile unstages a file // UnStageFile unstages a file
// we accept an array of filenames for the cases where a file has been renamed i.e. // we accept an array of filenames for the cases where a file has been renamed i.e.
// we accept the current name and the previous name // we accept the current name and the previous name
func (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error { func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error {
if tracked { for _, name := range fileNames {
return self.UnstageTrackedFiles(paths) var cmdArgs []string
} else { if reset {
return self.UnstageUntrackedFiles(paths) cmdArgs = NewGitCmd("reset").Arg("HEAD", "--", name).ToArgv()
} else {
cmdArgs = NewGitCmd("rm").Arg("--cached", "--force", "--", name).ToArgv()
}
err := self.cmd.New(cmdArgs).Run()
if err != nil {
return err
}
} }
} return nil
func (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error {
return self.cmd.New(NewGitCmd("reset").Arg("HEAD", "--").Arg(paths...).ToArgv()).Run()
}
func (self *WorkingTreeCommands) UnstageUntrackedFiles(paths []string) error {
return self.cmd.New(NewGitCmd("rm").Arg("--cached", "--force", "--").Arg(paths...).ToArgv()).Run()
} }
func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) { func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {
@@ -165,7 +169,6 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
if file.Added { if file.Added {
return self.os.RemoveFile(file.Name) return self.os.RemoveFile(file.Name)
} }
return self.DiscardUnstagedFileChanges(file) return self.DiscardUnstagedFileChanges(file)
} }
@@ -173,8 +176,6 @@ type IFileNode interface {
ForEachFile(cb func(*models.File) error) error ForEachFile(cb func(*models.File) error) error
GetFilePathsMatching(test func(*models.File) bool) []string GetFilePathsMatching(test func(*models.File) bool) []string
GetPath() string GetPath() string
// Returns file if the node is not a directory, otherwise returns nil
GetFile() *models.File
} }
func (self *WorkingTreeCommands) DiscardAllDirChanges(node IFileNode) error { func (self *WorkingTreeCommands) DiscardAllDirChanges(node IFileNode) error {
@@ -183,24 +184,13 @@ func (self *WorkingTreeCommands) DiscardAllDirChanges(node IFileNode) error {
} }
func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error { func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error {
file := node.GetFile() if err := self.RemoveUntrackedDirFiles(node); err != nil {
if file == nil { return err
if err := self.RemoveUntrackedDirFiles(node); err != nil { }
return err
}
cmdArgs := NewGitCmd("checkout").Arg("--", node.GetPath()).ToArgv() cmdArgs := NewGitCmd("checkout").Arg("--", node.GetPath()).ToArgv()
if err := self.cmd.New(cmdArgs).Run(); err != nil { if err := self.cmd.New(cmdArgs).Run(); err != nil {
return err return err
}
} else {
if file.Added && !file.HasStagedChanges {
return self.os.RemoveFile(file.Name)
}
if err := self.DiscardUnstagedFileChanges(file); err != nil {
return err
}
} }
return nil return nil
@@ -221,6 +211,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
return nil return nil
} }
// DiscardUnstagedFileChanges directly
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error { func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
cmdArgs := NewGitCmd("checkout").Arg("--", file.Name).ToArgv() cmdArgs := NewGitCmd("checkout").Arg("--", file.Name).ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
@@ -233,43 +224,38 @@ func (self *WorkingTreeCommands) Ignore(filename string) error {
// Exclude adds a file to the .git/info/exclude for the repo // Exclude adds a file to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filename string) error { func (self *WorkingTreeCommands) Exclude(filename string) error {
excludeFile := path.Join(self.repoPaths.repoGitDirPath, "info", "exclude") return self.os.AppendLineToFile(".git/info/exclude", filename)
return self.os.AppendLineToFile(excludeFile, filename)
} }
// WorktreeFileDiff returns the diff of a file // WorktreeFileDiff returns the diff of a file
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string { func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string {
// for now we assume an error means the file was deleted // for now we assume an error means the file was deleted
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput() s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
return s return s
} }
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj { func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
colorArg := self.UserConfig.Git.Paging.ColorArg colorArg := self.UserConfig.Git.Paging.ColorArg
if plain { if plain {
colorArg = "never" colorArg = "never"
} }
contextSize := self.AppState.DiffContextSize contextSize := self.UserConfig.Git.DiffContextSize
prevPath := node.GetPreviousPath() prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)). Arg(fmt.Sprintf("--color=%s", colorArg)).
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(cached, "--cached"). ArgIf(cached, "--cached").
ArgIf(noIndex, "--no-index"). ArgIf(noIndex, "--no-index").
Arg("--"). Arg("--").
ArgIf(noIndex, "/dev/null"). ArgIf(noIndex, "/dev/null").
Arg(node.GetPath()). Arg(node.GetPath()).
ArgIf(prevPath != "", prevPath). ArgIf(prevPath != "", prevPath).
Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
@@ -277,44 +263,42 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc // ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode. // but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool,
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput() ignoreWhitespace bool,
) (string, error) {
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain, ignoreWhitespace).RunWithOutput()
} }
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj { func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
contextSize := self.AppState.DiffContextSize ignoreWhitespace bool,
) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize
colorArg := self.UserConfig.Git.Paging.ColorArg colorArg := self.UserConfig.Git.Paging.ColorArg
if plain { if plain {
colorArg = "never" colorArg = "never"
} }
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--no-renames"). Arg("--no-renames").
Arg(fmt.Sprintf("--color=%s", colorArg)). Arg(fmt.Sprintf("--color=%s", colorArg)).
Arg(from). Arg(from).
Arg(to). Arg(to).
ArgIf(reverse, "-R"). ArgIf(reverse, "-R").
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg("--"). Arg("--").
Arg(fileName). Arg(fileName).
Dir(self.repoPaths.worktreePath).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// CheckoutFile checks out the file for the given commit // CheckoutFile checks out the file for the given commit
func (self *WorkingTreeCommands) CheckoutFile(commitHash, fileName string) error { func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
cmdArgs := NewGitCmd("checkout").Arg(commitHash, "--", fileName). cmdArgs := NewGitCmd("checkout").Arg(commitSha, "--", fileName).
ToArgv() ToArgv()
return self.cmd.New(cmdArgs).Run() return self.cmd.New(cmdArgs).Run()
@@ -345,7 +329,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
// ResetAndClean removes all unstaged changes and removes all untracked files // ResetAndClean removes all unstaged changes and removes all untracked files
func (self *WorkingTreeCommands) ResetAndClean() error { func (self *WorkingTreeCommands) ResetAndClean() error {
submoduleConfigs, err := self.submodule.GetConfigs(nil) submoduleConfigs, err := self.submodule.GetConfigs()
if err != nil { if err != nil {
return err return err
} }
@@ -363,7 +347,7 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
return self.RemoveUntrackedFiles() return self.RemoveUntrackedFiles()
} }
// ResetHard runs `git reset --hard` // ResetHardHead runs `git reset --hard`
func (self *WorkingTreeCommands) ResetHard(ref string) error { func (self *WorkingTreeCommands) ResetHard(ref string) error {
cmdArgs := NewGitCmd("reset").Arg("--hard", ref). cmdArgs := NewGitCmd("reset").Arg("--hard", ref).
ToArgv() ToArgv()
+36 -37
View File
@@ -61,6 +61,7 @@ func TestWorkingTreeUnstageFile(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.UnStageFile([]string{"test.txt"}, s.reset)) s.test(instance.UnStageFile([]string{"test.txt"}, s.reset))
@@ -189,6 +190,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile})
err := instance.DiscardAllFileChanges(s.file) err := instance.DiscardAllFileChanges(s.file)
@@ -229,7 +231,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "cached", testName: "cached",
@@ -243,7 +245,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "plain", testName: "plain",
@@ -257,7 +259,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "File not tracked and file has no staged changes", testName: "File not tracked and file has no staged changes",
@@ -271,7 +273,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
@@ -285,7 +287,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: true, ignoreWhitespace: true,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@@ -299,22 +301,18 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 17, contextSize: 17,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
appState := &config.AppState{} userConfig.Git.DiffContextSize = s.contextSize
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState, repoPaths: &repoPaths}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached) result := instance.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
assert.Equal(t, expectedResult, result) assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
@@ -345,7 +343,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@@ -356,7 +354,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 123, contextSize: 123,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
@@ -367,23 +365,19 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: true, ignoreWhitespace: true,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
}, },
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
appState := &config.AppState{} userConfig.Git.DiffContextSize = s.contextSize
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
appState.DiffContextSize = s.contextSize
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState, repoPaths: &repoPaths}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain) result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain, s.ignoreWhitespace)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, result) assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
@@ -393,18 +387,18 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
func TestWorkingTreeCheckoutFile(t *testing.T) { func TestWorkingTreeCheckoutFile(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
commitHash string commitSha string
fileName string fileName string
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
test func(error) test func(error)
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "typical case", testName: "typical case",
commitHash: "11af912", commitSha: "11af912",
fileName: "test999.txt", fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", nil), ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", nil),
test: func(err error) { test: func(err error) {
@@ -412,9 +406,9 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
}, },
}, },
{ {
testName: "returns error if there is one", testName: "returns error if there is one",
commitHash: "11af912", commitSha: "11af912",
fileName: "test999.txt", fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")), ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")),
test: func(err error) { test: func(err error) {
@@ -424,10 +418,11 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.CheckoutFile(s.commitHash, s.fileName)) s.test(instance.CheckoutFile(s.commitSha, s.fileName))
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
} }
@@ -454,6 +449,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.DiscardUnstagedFileChanges(s.file)) s.test(instance.DiscardUnstagedFileChanges(s.file))
@@ -481,6 +477,7 @@ func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.DiscardAnyUnstagedFileChanges()) s.test(instance.DiscardAnyUnstagedFileChanges())
@@ -508,6 +505,7 @@ func TestWorkingTreeRemoveUntrackedFiles(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.RemoveUntrackedFiles()) s.test(instance.RemoveUntrackedFiles())
@@ -537,6 +535,7 @@ func TestWorkingTreeResetHard(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner}) instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.ResetHard(s.ref)) s.test(instance.ResetHard(s.ref))

Some files were not shown because too many files have changed in this diff Show More