mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 10:36:45 -04:00
Compare commits
166
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a8b05401 | ||
|
|
de22238589 | ||
|
|
52a2e4c1dc | ||
|
|
e953659ebf | ||
|
|
b33ec5a050 | ||
|
|
fbfec75a99 | ||
|
|
3e73dacce3 | ||
|
|
684d1e955e | ||
|
|
f67824b349 | ||
|
|
c53752a5f9 | ||
|
|
dc163bfc4d | ||
|
|
3c436b3457 | ||
|
|
526d02de1c | ||
|
|
a905a28e41 | ||
|
|
5964472ec1 | ||
|
|
c77c02c879 | ||
|
|
d26350502c | ||
|
|
2ddd3ddfc0 | ||
|
|
df3cd941d7 | ||
|
|
ea28529cbb | ||
|
|
db592e1dc6 | ||
|
|
863a65cf94 | ||
|
|
5eeaebde98 | ||
|
|
e059641f3f | ||
|
|
fbac05fff7 | ||
|
|
99e05ee138 | ||
|
|
7144691ae5 | ||
|
|
fb170b55a2 | ||
|
|
903e65ae8d | ||
|
|
e56b05cbac | ||
|
|
9e91aa8b43 | ||
|
|
7977c2deb5 | ||
|
|
d7fb441a3c | ||
|
|
f03e1e4e42 | ||
|
|
91f83c6be7 | ||
|
|
fd581ef70c | ||
|
|
f98b2edae5 | ||
|
|
97ced9e14f | ||
|
|
ea09686770 | ||
|
|
c769e5828c | ||
|
|
33410213f1 | ||
|
|
af57dbd225 | ||
|
|
66a253916e | ||
|
|
188890fc49 | ||
|
|
3e15315339 | ||
|
|
4fa8586191 | ||
|
|
3a295f34df | ||
|
|
1ac3ae1ad1 | ||
|
|
34404162e6 | ||
|
|
e6e95343af | ||
|
|
52316e628e | ||
|
|
e8b97c9fe2 | ||
|
|
ab03cf8bcf | ||
|
|
cc01c0de6d | ||
|
|
4f471f352f | ||
|
|
a63d5c5644 | ||
|
|
a47e72892a | ||
|
|
c4e71356bb | ||
|
|
b0f7cf092f | ||
|
|
808b35d8f2 | ||
|
|
4aa9147dfa | ||
|
|
14ec0cd92e | ||
|
|
36c6462a53 | ||
|
|
2ec0b671e6 | ||
|
|
3103398e31 | ||
|
|
e78e829e3a | ||
|
|
eceb3a5aa6 | ||
|
|
8a9eefa4d2 | ||
|
|
11316b7a48 | ||
|
|
8f3ccd07db | ||
|
|
79334b84f0 | ||
|
|
39e84e13f4 | ||
|
|
0c3eab4059 | ||
|
|
05089b9a9a | ||
|
|
d90fedfbf8 | ||
|
|
6af0afdb1c | ||
|
|
6cb5e628b3 | ||
|
|
a4239c7a37 | ||
|
|
fc0b14edef | ||
|
|
6b9f43c73b | ||
|
|
1358662a21 | ||
|
|
ffe5e16688 | ||
|
|
575afa1377 | ||
|
|
d22f6d73ff | ||
|
|
867975ad31 | ||
|
|
24a07ae85a | ||
|
|
c370a5e728 | ||
|
|
dba0edb998 | ||
|
|
69040f094d | ||
|
|
8c46a0110d | ||
|
|
a30d924afe | ||
|
|
f4c188fa5b | ||
|
|
7b4b42abd6 | ||
|
|
2f76ef58f1 | ||
|
|
e436922eb6 | ||
|
|
056cc14821 | ||
|
|
c686c7f9ed | ||
|
|
b27a2ce39a | ||
|
|
e3f21f0588 | ||
|
|
3375cc1b3d | ||
|
|
ed98b60078 | ||
|
|
e76fa5a6cb | ||
|
|
a77aa4d75a | ||
|
|
68a2a4c6b5 | ||
|
|
8858f03606 | ||
|
|
5670c0a301 | ||
|
|
c953871ec7 | ||
|
|
b0c19b291f | ||
|
|
8a022ddf0e | ||
|
|
e16f1ba84f | ||
|
|
19df238b77 | ||
|
|
092363a986 | ||
|
|
23d39c79b2 | ||
|
|
0141bbde0e | ||
|
|
4c7d363959 | ||
|
|
41f86f6535 | ||
|
|
90feb4bae6 | ||
|
|
1d40bd1707 | ||
|
|
17df42e517 | ||
|
|
fd66499c8f | ||
|
|
e001183768 | ||
|
|
268d8f99b8 | ||
|
|
212e19f598 | ||
|
|
1c82924307 | ||
|
|
c8066c54b5 | ||
|
|
438038a4f1 | ||
|
|
f23547580a | ||
|
|
3d79c6a3d3 | ||
|
|
bc8050d8ac | ||
|
|
b8900baf1a | ||
|
|
dcbebef897 | ||
|
|
7e9dffe1b9 | ||
|
|
c81333fefe | ||
|
|
5639af9918 | ||
|
|
ee348751a0 | ||
|
|
a92f0f7c89 | ||
|
|
6dca3e1766 | ||
|
|
850a82784a | ||
|
|
74acb3e86a | ||
|
|
9351af3829 | ||
|
|
7af7af27c6 | ||
|
|
db9373662a | ||
|
|
e189546acb | ||
|
|
c7733aa5e5 | ||
|
|
a0fd47348b | ||
|
|
6feb301c2a | ||
|
|
e66b162726 | ||
|
|
4f8816ebf2 | ||
|
|
50cf7ac5bc | ||
|
|
088445b7be | ||
|
|
c7fd218308 | ||
|
|
7ddb80a13e | ||
|
|
fecf2ab810 | ||
|
|
3232f46a8b | ||
|
|
7b757d1cfe | ||
|
|
843488bff4 | ||
|
|
a9d4ff2aee | ||
|
|
5d5471c017 | ||
|
|
8cdfc6758f | ||
|
|
010f430d1f | ||
|
|
79620fc6cf | ||
|
|
47f84b6aea | ||
|
|
de6d278e57 | ||
|
|
46b08e7d70 | ||
|
|
eb9fbb0a33 | ||
|
|
6422b399e7 |
@@ -0,0 +1,13 @@
|
||||
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
|
||||
|
||||
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
|
||||
ARG VARIANT=1-bullseye
|
||||
FROM golang:${VARIANT}
|
||||
|
||||
RUN go install mvdan.cc/gofumpt@latest
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.0
|
||||
RUN golangci-lint --version
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
@@ -0,0 +1,69 @@
|
||||
// adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/devcontainer.json
|
||||
{
|
||||
"build": {
|
||||
"dockerfile": "./Dockerfile",
|
||||
"context": "."
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/common-utils:1": {
|
||||
"installZsh": "true",
|
||||
"username": "vscode",
|
||||
"uid": "1000",
|
||||
"gid": "1000",
|
||||
"upgradePackages": "true"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "none"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git:1": {
|
||||
"version": "latest",
|
||||
"ppa": "false"
|
||||
}
|
||||
},
|
||||
"overrideFeatureInstallOrder": [
|
||||
"ghcr.io/devcontainers/features/common-utils"
|
||||
],
|
||||
// not sure if we actually need these
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_PTRACE",
|
||||
"--security-opt",
|
||||
"seccomp=unconfined"
|
||||
],
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"go.toolsManagement.checkForUpdates": "local",
|
||||
"go.useLanguageServer": true,
|
||||
"go.gopath": "/go",
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
"usePlaceholders": false // add parameter placeholders when completing a function
|
||||
},
|
||||
"files.eol": "\n"
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"golang.Go"
|
||||
]
|
||||
}
|
||||
},
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "go version",
|
||||
|
||||
// See https://www.kenmuse.com/blog/avoiding-dubious-ownership-in-dev-containers/ for the safe.directory part
|
||||
// The defaultBranch part is required for our deprecated integration tests.
|
||||
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && git config --global init.defaultBranch master",
|
||||
|
||||
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -73,8 +73,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Test code
|
||||
# for file.allow thing see https://vielmetti.typepad.com/logbook/2022/10/git-security-fixes-lead-to-fatal-transport-file-not-allowed-error-in-ci-systems-cve-2022-39253.html
|
||||
run: |
|
||||
PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go
|
||||
git config --global protocol.file.allow always && PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Integration Tests"
|
||||
|
||||
@@ -27,6 +27,7 @@ lazygit.exe
|
||||
!.circleci/
|
||||
!.github/
|
||||
!.vscode/
|
||||
!.devcontainer/
|
||||
|
||||
# these are for our integration tests
|
||||
!.git_keep
|
||||
|
||||
+1
-5
@@ -32,12 +32,8 @@ checksum:
|
||||
snapshot:
|
||||
name_template: '{{ .Tag }}-next'
|
||||
changelog:
|
||||
use: github-native
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^bump'
|
||||
brews:
|
||||
-
|
||||
# Repository to push the tap to.
|
||||
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Generate cheatsheet",
|
||||
"type": "process",
|
||||
"command": "go run scripts/cheatsheet/main.go ",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Bump gocui",
|
||||
"type": "shell",
|
||||
"command": "./scripts/bump_gocui.sh",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Bump lazycore",
|
||||
"type": "shell",
|
||||
"command": "./scripts/bump_lazycore.sh",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test (slow)",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test (sandbox)",
|
||||
"type": "shell",
|
||||
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
+66
-1
@@ -23,6 +23,26 @@ welcome your pull requests:
|
||||
|
||||
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
|
||||
|
||||
If you want to spare yourself the hassle of setting up your dev environment yourself (i.e. installing Go, extensions, and extra tools), you can run the Lazygit code in a VSCode dev container like so:
|
||||
|
||||

|
||||
|
||||
This requires that:
|
||||
* you have docker installed
|
||||
* you have the dev containers extension installed in VSCode
|
||||
|
||||
See [here](https://code.visualstudio.com/docs/devcontainers/containers) for more info about dev containers.
|
||||
|
||||
## Running in a Github Codespace
|
||||
|
||||
If you want to start contributing to Lazygit with the click of a button, you can open the lazygit codebase in a Codespace:
|
||||
|
||||

|
||||
|
||||
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
Please note by participating in this project, you agree to abide by the [code of conduct].
|
||||
@@ -132,6 +152,34 @@ If you want to trigger a debug session from VSCode, you can use the following sn
|
||||
}
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
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
|
||||
|
||||
Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
|
||||
@@ -140,7 +188,7 @@ Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go
|
||||
|
||||
Sometimes you will need to make a change in the gocui fork (https://github.com/jesseduffield/gocui). Gocui is the package responsible for rendering windows and handling user input. Here's the typical process to follow:
|
||||
|
||||
1. Make the changes in gocui inside the vendor directory so it's easy to test against lazygit
|
||||
1. Make the changes in gocui inside lazygit's vendor directory so it's easy to test against lazygit
|
||||
2. Copy the changes over to the actual gocui repo (clone it if you haven't already, and use the `awesome` branch, not `master`)
|
||||
3. Raise a PR on the gocui repo with your changes
|
||||
4. After that PR is merged, make a PR in lazygit bumping the gocui version. You can bump the version by running the following at the lazygit repo root:
|
||||
@@ -151,6 +199,23 @@ Sometimes you will need to make a change in the gocui fork (https://github.com/j
|
||||
|
||||
5. Raise a PR in lazygit with those changes
|
||||
|
||||
## Updating Lazycore
|
||||
|
||||
[Lazycore](https://github.com/jesseduffield/lazycore) is a repo containing shared functionality between lazygit and lazydocker. Sometimes you will need to make a change to that repo and import the changes into lazygit. Similar to updating Gocui, here's what you do:
|
||||
|
||||
1. Make the changes in lazycore inside lazygit's vendor directory so it's easy to test against lazygit
|
||||
2. Copy the changes over to the actual lazycore repo (clone it if you haven't already, and use the `master` branch)
|
||||
3. Raise a PR on the lazycore repo with your changes
|
||||
4. After that PR is merged, make a PR in lazygit bumping the lazycore version. You can bump the version by running the following at the lazygit repo root:
|
||||
|
||||
```sh
|
||||
./scripts/bump_lazycore.sh
|
||||
```
|
||||
|
||||
Or if you're using VSCode, there is a bump lazycore task you can find by going `cmd+shift+p` and typing 'Run task'
|
||||
|
||||
5. Raise a PR in lazygit with those changes
|
||||
|
||||
## Improvements
|
||||
|
||||
If you can think of any way to improve these docs let us know.
|
||||
|
||||
@@ -13,7 +13,7 @@ Usage:
|
||||
See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md
|
||||
|
||||
CLI mode:
|
||||
> go run cmd/integration_test/main.go cli [--slow] <test1> <test2> ...
|
||||
> go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ...
|
||||
If you pass no test names, it runs all tests
|
||||
Accepted environment variables:
|
||||
KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
|
||||
@@ -42,12 +42,19 @@ func main() {
|
||||
case "cli":
|
||||
testNames := os.Args[2:]
|
||||
slow := false
|
||||
sandbox := false
|
||||
// get the next arg if it's --slow
|
||||
if len(os.Args) > 2 && (os.Args[2] == "--slow" || os.Args[2] == "-slow") {
|
||||
testNames = os.Args[3:]
|
||||
slow = true
|
||||
if len(os.Args) > 2 {
|
||||
if os.Args[2] == "--slow" || os.Args[2] == "-slow" {
|
||||
testNames = os.Args[3:]
|
||||
slow = true
|
||||
} else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" {
|
||||
testNames = os.Args[3:]
|
||||
sandbox = true
|
||||
}
|
||||
}
|
||||
clients.RunCLI(testNames, slow)
|
||||
|
||||
clients.RunCLI(testNames, slow, sandbox)
|
||||
case "tui":
|
||||
clients.RunTUI()
|
||||
default:
|
||||
|
||||
+13
-18
@@ -12,6 +12,10 @@ For old installations (slightly embarrassing: I didn't realise at the time that
|
||||
- MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||
- Windows: `%APPDATA%\jesseduffield\lazygit\config.yml`
|
||||
|
||||
If you want to change the config directory:
|
||||
|
||||
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
|
||||
|
||||
## Default
|
||||
|
||||
```yaml
|
||||
@@ -25,7 +29,6 @@ gui:
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko'
|
||||
timeFormat: '02 Jan 06 15:04 MST' # https://pkg.go.dev/time#Time.Format
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
- green
|
||||
- bold
|
||||
@@ -219,6 +222,7 @@ keybinding:
|
||||
viewBisectOptions: 'b'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
renameStash: 'r'
|
||||
commitFiles:
|
||||
checkoutCommitFile: 'c'
|
||||
main:
|
||||
@@ -339,23 +343,6 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
theme:
|
||||
lightTheme: true
|
||||
activeBorderColor:
|
||||
- black
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
selectedLineBgColor:
|
||||
- default
|
||||
```
|
||||
|
||||
## 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` 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:
|
||||
@@ -431,6 +418,14 @@ gui:
|
||||
|
||||
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 `null`.
|
||||
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
edit: null # disable 'edit file'
|
||||
```
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -8,7 +8,7 @@ customCommands:
|
||||
command: 'hub browse -- "commit/{{.SelectedLocalCommit.Sha}}"'
|
||||
context: 'commits'
|
||||
- key: 'a'
|
||||
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name}}"
|
||||
command: "git {{if .SelectedFile.HasUnstagedChanges}} add {{else}} reset {{end}} {{.SelectedFile.Name | quote}}"
|
||||
context: 'files'
|
||||
description: 'toggle file staged'
|
||||
- key: 'C'
|
||||
|
||||
@@ -238,6 +238,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>r</kbd>: Stashを変更
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: laten vallen
|
||||
<kbd>n</kbd>: nieuwe branch
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -231,6 +231,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
<kbd>n</kbd>: nowa gałąź
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -264,6 +264,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>g</kbd>: 应用并删除
|
||||
<kbd>d</kbd>: 删除
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd>enter</kbd>: 查看提交的文件
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -11,33 +11,34 @@ require (
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell/v2 v2.5.2
|
||||
github.com/gdamore/tcell/v2 v2.5.3
|
||||
github.com/go-errors/errors v1.4.2
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220815095708-156fda5e0419
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
github.com/jesseduffield/yaml v2.1.0+incompatible
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/kyokomi/emoji/v2 v2.2.8
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.13
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/samber/lo v1.10.1
|
||||
github.com/samber/lo v1.31.0
|
||||
github.com/sanity-io/litter v1.5.2
|
||||
github.com/sasha-s/go-deadlock v0.3.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -61,14 +62,14 @@ require (
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/rivo/uniseg v0.3.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
|
||||
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -35,8 +35,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
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/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/gdamore/tcell/v2 v2.5.2 h1:tKzG29kO9p2V++3oBY2W9zUjYu7IK1MENFeY/BzJSVY=
|
||||
github.com/gdamore/tcell/v2 v2.5.2/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
@@ -70,12 +70,14 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
|
||||
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/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220815095708-156fda5e0419 h1:p3Ix7RUcy4X16Lk5jTSfTxecJT7ryqYHclfRbo/Svzs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20220815095708-156fda5e0419/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
|
||||
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/gocui v0.3.1-0.20221016041636-16c24668f71c h1:ttqCvM86hyp4V3DtRPxLo9imD1s+n6ry/zshPx0tt98=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20221016041636-16c24668f71c/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
|
||||
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/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
|
||||
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/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
|
||||
@@ -112,8 +114,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||
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-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/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/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -134,12 +137,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
|
||||
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/samber/lo v1.10.1 h1:0D3h7i0U3hRAbaCeQ82DLe67n0A7Bbl0/cEoWqFGp+U=
|
||||
github.com/samber/lo v1.10.1/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
|
||||
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
|
||||
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
|
||||
@@ -151,14 +154,17 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
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/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
@@ -194,15 +200,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
|
||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -222,5 +229,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -77,7 +76,7 @@ func (self *rebaseDaemon) Run() error {
|
||||
self.c.Log.Info("args: ", os.Args)
|
||||
|
||||
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
|
||||
if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
|
||||
if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||
|
||||
+15
-1
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/logs"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -57,6 +58,11 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if isRepo, err := isDirectoryAGitRepository(absRepoPath); err != nil || !isRepo {
|
||||
log.Fatal(absRepoPath + " is not a valid git repository.")
|
||||
}
|
||||
|
||||
cliArgs.WorkTree = absRepoPath
|
||||
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
|
||||
}
|
||||
@@ -78,7 +84,8 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
}
|
||||
|
||||
if cliArgs.PrintVersionInfo {
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH)
|
||||
gitVersion := getGitVersionInfo()
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH, gitVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -263,3 +270,10 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
|
||||
buildInfo.Date = time.Value
|
||||
}
|
||||
}
|
||||
|
||||
func getGitVersionInfo() string {
|
||||
cmd := secureexec.Command("git", "--version")
|
||||
stdout, _ := cmd.Output()
|
||||
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
|
||||
return gitVersion
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -26,7 +26,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
|
||||
func newProductionLogger() *logrus.Logger {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
log.Out = io.Discard
|
||||
log.SetLevel(logrus.ErrorLevel)
|
||||
return log
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package cheatsheet
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func Check() {
|
||||
dir := GetDir()
|
||||
dir := GetKeybindingsDir()
|
||||
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
@@ -60,7 +59,7 @@ func obtainContent(dir string) string {
|
||||
content := ""
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if re.MatchString(path) {
|
||||
bytes, err := ioutil.ReadFile(path)
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ import (
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -44,8 +44,8 @@ func CommandToRun() string {
|
||||
return "go run scripts/cheatsheet/main.go generate"
|
||||
}
|
||||
|
||||
func GetDir() string {
|
||||
return utils.GetLazygitRootDirectory() + "/docs/keybindings"
|
||||
func GetKeybindingsDir() string {
|
||||
return utils.GetLazyRootDirectory() + "/docs/keybindings"
|
||||
}
|
||||
|
||||
func generateAtDir(cheatsheetDir string) {
|
||||
@@ -75,7 +75,7 @@ func generateAtDir(cheatsheetDir string) {
|
||||
}
|
||||
|
||||
func Generate() {
|
||||
generateAtDir(GetDir())
|
||||
generateAtDir(GetKeybindingsDir())
|
||||
}
|
||||
|
||||
func writeString(file *os.File, str string) {
|
||||
|
||||
+28
-21
@@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -43,14 +41,14 @@ type GitCommand struct {
|
||||
}
|
||||
|
||||
type Loaders struct {
|
||||
Branches *loaders.BranchLoader
|
||||
CommitFiles *loaders.CommitFileLoader
|
||||
Commits *loaders.CommitLoader
|
||||
Files *loaders.FileLoader
|
||||
ReflogCommits *loaders.ReflogCommitLoader
|
||||
Remotes *loaders.RemoteLoader
|
||||
Stash *loaders.StashLoader
|
||||
Tags *loaders.TagLoader
|
||||
BranchLoader *git_commands.BranchLoader
|
||||
CommitFileLoader *git_commands.CommitFileLoader
|
||||
CommitLoader *git_commands.CommitLoader
|
||||
FileLoader *git_commands.FileLoader
|
||||
ReflogCommitLoader *git_commands.ReflogCommitLoader
|
||||
RemoteLoader *git_commands.RemoteLoader
|
||||
StashLoader *git_commands.StashLoader
|
||||
TagLoader *git_commands.TagLoader
|
||||
}
|
||||
|
||||
func NewGitCommand(
|
||||
@@ -68,7 +66,7 @@ func NewGitCommand(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
|
||||
dotGitDir, err := findDotGitDir(os.Stat, os.ReadFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,10 +97,11 @@ func NewGitCommandAux(
|
||||
// on the one struct.
|
||||
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
||||
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
||||
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
|
||||
|
||||
fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
|
||||
|
||||
gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
|
||||
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
|
||||
flowCommands := git_commands.NewFlowCommands(gitCommon)
|
||||
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
|
||||
branchCommands := git_commands.NewBranchCommands(gitCommon)
|
||||
@@ -120,6 +119,14 @@ func NewGitCommandAux(
|
||||
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchManager)
|
||||
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
||||
|
||||
return &GitCommand{
|
||||
Branch: branchCommands,
|
||||
Commit: commitCommands,
|
||||
@@ -138,14 +145,14 @@ func NewGitCommandAux(
|
||||
Bisect: bisectCommands,
|
||||
WorkingTree: workingTreeCommands,
|
||||
Loaders: Loaders{
|
||||
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands),
|
||||
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
|
||||
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
|
||||
Files: fileLoader,
|
||||
ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd),
|
||||
Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes),
|
||||
Stash: loaders.NewStashLoader(cmn, cmd),
|
||||
Tags: loaders.NewTagLoader(cmn, cmd),
|
||||
BranchLoader: branchLoader,
|
||||
CommitFileLoader: commitFileLoader,
|
||||
CommitLoader: commitLoader,
|
||||
FileLoader: fileLoader,
|
||||
ReflogCommitLoader: reflogCommitLoader,
|
||||
RemoteLoader: remoteLoader,
|
||||
StashLoader: stashLoader,
|
||||
TagLoader: tagLoader,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, e
|
||||
return self.innerRunner.RunWithOutput(cmdObj)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
|
||||
return self.innerRunner.RunWithOutputs(cmdObj)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||
}
|
||||
|
||||
@@ -30,18 +30,20 @@ func (self *BranchCommands) New(name string, base string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
|
||||
}
|
||||
|
||||
// CurrentBranchName get the current branch name and displayname.
|
||||
// the first returned string is the name and the second is the displayname
|
||||
// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
|
||||
func (self *BranchCommands) CurrentBranchName() (string, string, error) {
|
||||
// CurrentBranchInfo get the current branch information.
|
||||
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
|
||||
if err == nil && branchName != "HEAD\n" {
|
||||
trimmedBranchName := strings.TrimSpace(branchName)
|
||||
return trimmedBranchName, trimmedBranchName, nil
|
||||
return BranchInfo{
|
||||
RefName: trimmedBranchName,
|
||||
DisplayName: trimmedBranchName,
|
||||
DetachedHead: false,
|
||||
}, nil
|
||||
}
|
||||
output, err := self.cmd.New("git branch --contains").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return BranchInfo{}, err
|
||||
}
|
||||
for _, line := range utils.SplitLines(output) {
|
||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||
@@ -49,10 +51,18 @@ func (self *BranchCommands) CurrentBranchName() (string, string, error) {
|
||||
if len(match) > 0 {
|
||||
branchName = match[1]
|
||||
displayBranchName := match[0][2:]
|
||||
return branchName, displayBranchName, nil
|
||||
return BranchInfo{
|
||||
RefName: branchName,
|
||||
DisplayName: displayBranchName,
|
||||
DetachedHead: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return "HEAD", "HEAD", nil
|
||||
return BranchInfo{
|
||||
RefName: "HEAD",
|
||||
DisplayName: "HEAD",
|
||||
DetachedHead: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete delete branch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -27,24 +27,30 @@ type BranchLoaderConfigCommands interface {
|
||||
Branches() (map[string]*config.Branch, error)
|
||||
}
|
||||
|
||||
type BranchInfo struct {
|
||||
RefName string
|
||||
DisplayName string // e.g. '(HEAD detached at 123asdf)'
|
||||
DetachedHead bool
|
||||
}
|
||||
|
||||
// BranchLoader returns a list of Branch objects for the current repo
|
||||
type BranchLoader struct {
|
||||
*common.Common
|
||||
getRawBranches func() (string, error)
|
||||
getCurrentBranchName func() (string, string, error)
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
config BranchLoaderConfigCommands
|
||||
}
|
||||
|
||||
func NewBranchLoader(
|
||||
cmn *common.Common,
|
||||
getRawBranches func() (string, error),
|
||||
getCurrentBranchName func() (string, string, error),
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
config BranchLoaderConfigCommands,
|
||||
) *BranchLoader {
|
||||
return &BranchLoader{
|
||||
Common: cmn,
|
||||
getRawBranches: getRawBranches,
|
||||
getCurrentBranchName: getCurrentBranchName,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
@@ -84,11 +90,11 @@ outer:
|
||||
}
|
||||
}
|
||||
if !foundHead {
|
||||
currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName()
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
branches = slices.Prepend(branches, &models.Branch{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"})
|
||||
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
|
||||
}
|
||||
|
||||
configBranches, err := self.config.Branches()
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
|
||||
import (
|
||||
@@ -53,10 +53,10 @@ func TestBranchGetCommitDifferences(t *testing.T) {
|
||||
|
||||
func TestBranchNewBranch(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
Expect(`git checkout -b "test" "master"`, "", nil)
|
||||
Expect(`git checkout -b "test" "refs/heads/master"`, "", nil)
|
||||
instance := buildBranchCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.New("test", "master"))
|
||||
assert.NoError(t, instance.New("test", "refs/heads/master"))
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
@@ -162,32 +162,34 @@ func TestBranchGetAllBranchGraph(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestBranchCurrentBranchName(t *testing.T) {
|
||||
func TestBranchCurrentBranchInfo(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(string, string, error)
|
||||
test func(BranchInfo, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"says we are on the master branch if we are",
|
||||
oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", name)
|
||||
assert.EqualValues(t, "master", displayname)
|
||||
assert.EqualValues(t, "master", info.RefName)
|
||||
assert.EqualValues(t, "master", info.DisplayName)
|
||||
assert.False(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
"falls back to git `git branch --contains` if symbolic-ref fails",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "* master", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
Expect(`git branch --contains`, "* (HEAD detached at 8982166a)", nil),
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", name)
|
||||
assert.EqualValues(t, "master", displayname)
|
||||
assert.EqualValues(t, "8982166a", info.RefName)
|
||||
assert.EqualValues(t, "(HEAD detached at 8982166a)", info.DisplayName)
|
||||
assert.True(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -195,10 +197,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "* (HEAD detached at 123abcd)", nil),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "123abcd", name)
|
||||
assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
|
||||
assert.EqualValues(t, "123abcd", info.RefName)
|
||||
assert.EqualValues(t, "(HEAD detached at 123abcd)", info.DisplayName)
|
||||
assert.True(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -206,10 +209,11 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
|
||||
Expect(`git branch --contains`, "", errors.New("error")),
|
||||
func(name string, displayname string, err error) {
|
||||
func(info BranchInfo, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, "", name)
|
||||
assert.EqualValues(t, "", displayname)
|
||||
assert.EqualValues(t, "", info.RefName)
|
||||
assert.EqualValues(t, "", info.DisplayName)
|
||||
assert.False(t, info.DetachedHead)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -218,7 +222,7 @@ func TestBranchCurrentBranchName(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.CurrentBranchName())
|
||||
s.test(instance.CurrentBranchInfo())
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,9 +1,8 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -30,7 +29,7 @@ type CommitLoader struct {
|
||||
*common.Common
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
|
||||
getCurrentBranchName func() (string, string, error)
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
getRebaseMode func() (enums.RebaseMode, error)
|
||||
readFile func(filename string) ([]byte, error)
|
||||
walkFiles func(root string, fn filepath.WalkFunc) error
|
||||
@@ -42,15 +41,15 @@ func NewCommitLoader(
|
||||
cmn *common.Common,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dotGitDir string,
|
||||
getCurrentBranchName func() (string, string, error),
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
getRebaseMode func() (enums.RebaseMode, error),
|
||||
) *CommitLoader {
|
||||
return &CommitLoader{
|
||||
Common: cmn,
|
||||
cmd: cmd,
|
||||
getCurrentBranchName: getCurrentBranchName,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: ioutil.ReadFile,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
}
|
||||
@@ -372,13 +371,13 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
|
||||
currentBranch, _, err := self.getCurrentBranchName()
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseBranch := "master"
|
||||
if strings.HasPrefix(currentBranch, "feature/") {
|
||||
if strings.HasPrefix(info.RefName, "feature/") {
|
||||
baseBranch = "develop"
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
@@ -182,8 +182,8 @@ func TestGetCommits(t *testing.T) {
|
||||
builder := &CommitLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
getCurrentBranchName: func() (string, string, error) {
|
||||
return scenario.currentBranchName, scenario.currentBranchName, nil
|
||||
getCurrentBranchInfo: func() (BranchInfo, error) {
|
||||
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
|
||||
},
|
||||
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
|
||||
dotGitDir: ".git",
|
||||
@@ -1,10 +1,11 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
@@ -70,6 +71,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
GetenvFn: getenv,
|
||||
Cmd: cmd,
|
||||
RemoveFileFn: removeFile,
|
||||
TempDir: os.TempDir(),
|
||||
})
|
||||
|
||||
gitCommon.dotGitDir = deps.dotGitDir
|
||||
@@ -86,8 +88,8 @@ func buildRepo() *gogit.Repository {
|
||||
return repo
|
||||
}
|
||||
|
||||
func buildFileLoader(gitCommon *GitCommon) *loaders.FileLoader {
|
||||
return loaders.NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
|
||||
func buildFileLoader(gitCommon *GitCommon) *FileLoader {
|
||||
return NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
|
||||
}
|
||||
|
||||
func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -20,7 +20,7 @@ func NewFileCommands(gitCommon *GitCommon) *FileCommands {
|
||||
|
||||
// Cat obtains the content of a file
|
||||
func (self *FileCommands) Cat(fileName string) (string, error) {
|
||||
buf, err := ioutil.ReadFile(fileName)
|
||||
buf, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -87,7 +87,7 @@ func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
noRenamesFlag = " --no-renames"
|
||||
}
|
||||
|
||||
statusLines, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutput()
|
||||
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
|
||||
if err != nil {
|
||||
return []FileStatus{}, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -2,7 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -202,7 +202,7 @@ func (self *RebaseCommands) AmendTo(sha string) error {
|
||||
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := ioutil.ReadFile(fileName)
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
|
||||
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
|
||||
result := strings.Join(content, "\n")
|
||||
|
||||
return ioutil.WriteFile(fileName, []byte(result), 0o644)
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
|
||||
@@ -234,7 +234,7 @@ func (self *RebaseCommands) getTodoCommitCount(content []string) int {
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := ioutil.ReadFile(fileName)
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...)
|
||||
result := strings.Join(rearrangedContent, "\n")
|
||||
|
||||
return ioutil.WriteFile(fileName, []byte(result), 0o644)
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -255,7 +255,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error {
|
||||
return self.runSkipEditorCommand(
|
||||
self.cmd.New(
|
||||
fmt.Sprintf(
|
||||
"git rebase --interactive --autostash --autosquash %s^",
|
||||
"git rebase --interactive --rebase-merges --autostash --autosquash %s^",
|
||||
sha,
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -2,20 +2,20 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
type StashCommands struct {
|
||||
*GitCommon
|
||||
fileLoader *loaders.FileLoader
|
||||
fileLoader *FileLoader
|
||||
workingTree *WorkingTreeCommands
|
||||
}
|
||||
|
||||
func NewStashCommands(
|
||||
gitCommon *GitCommon,
|
||||
fileLoader *loaders.FileLoader,
|
||||
fileLoader *FileLoader,
|
||||
workingTree *WorkingTreeCommands,
|
||||
) *StashCommands {
|
||||
return &StashCommands{
|
||||
@@ -46,6 +46,19 @@ func (self *StashCommands) Save(message string) error {
|
||||
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Store(sha string, message string) error {
|
||||
trimmedMessage := strings.Trim(message, " \t")
|
||||
if len(trimmedMessage) > 0 {
|
||||
return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run()
|
||||
}
|
||||
return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Sha(index int) (string, error) {
|
||||
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
|
||||
return strings.Trim(sha, "\r\n"), err
|
||||
}
|
||||
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
|
||||
|
||||
@@ -97,7 +110,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
// meaning it's deleted in your working tree but added in your index. Given that it's
|
||||
// now safely stashed, we need to remove it.
|
||||
files := self.fileLoader.
|
||||
GetStatusFiles(loaders.GetStatusFileOptions{})
|
||||
GetStatusFiles(GetStatusFileOptions{})
|
||||
|
||||
for _, file := range files {
|
||||
if file.ShortStatus == "AD" {
|
||||
@@ -109,3 +122,25 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Rename(index int, message string) error {
|
||||
sha, err := self.Sha(index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.Drop(index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = self.Store(sha, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@@ -65,8 +65,8 @@ outer:
|
||||
}
|
||||
|
||||
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||
rawString, _ := self.cmd.New("git stash list --pretty='%gs'").DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitLines(rawString), func(line string, index int) *models.StashEntry {
|
||||
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return self.stashEntryFromLine(line, index)
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -22,7 +22,7 @@ func TestGetStashEntries(t *testing.T) {
|
||||
"No stash entries found",
|
||||
"",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(`git stash list --pretty='%gs'`, "", nil),
|
||||
Expect(`git stash list -z --pretty='%gs'`, "", nil),
|
||||
[]*models.StashEntry{},
|
||||
},
|
||||
{
|
||||
@@ -30,8 +30,8 @@ func TestGetStashEntries(t *testing.T) {
|
||||
"",
|
||||
oscommands.NewFakeRunner(t).
|
||||
Expect(
|
||||
`git stash list --pretty='%gs'`,
|
||||
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template",
|
||||
`git stash list -z --pretty='%gs'`,
|
||||
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
|
||||
nil,
|
||||
),
|
||||
[]*models.StashEntry{
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestStashDrop(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "", nil)
|
||||
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Drop(1))
|
||||
@@ -44,6 +44,59 @@ func TestStashSave(t *testing.T) {
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestStashStore(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
sha string
|
||||
message string
|
||||
expected []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Non-empty message",
|
||||
sha: "0123456789abcdef",
|
||||
message: "New stash name",
|
||||
expected: []string{"stash", "store", "0123456789abcdef", "-m", "New stash name"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
sha: "0123456789abcdef",
|
||||
message: "",
|
||||
expected: []string{"stash", "store", "0123456789abcdef"},
|
||||
},
|
||||
{
|
||||
testName: "Space message",
|
||||
sha: "0123456789abcdef",
|
||||
message: " ",
|
||||
expected: []string{"stash", "store", "0123456789abcdef"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expected, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Store(s.sha, s.message))
|
||||
runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashSha(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"rev-parse", "refs/stash@{5}"}, "14d94495194651adfd5f070590df566c11d28243\n", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
sha, err := instance.Sha(5)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "14d94495194651adfd5f070590df566c11d28243", sha)
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
@@ -79,3 +132,50 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashRename(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
index int
|
||||
message string
|
||||
expectedShaCmd []string
|
||||
shaResult string
|
||||
expectedDropCmd []string
|
||||
expectedStoreCmd []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Default case",
|
||||
index: 3,
|
||||
message: "New message",
|
||||
expectedShaCmd: []string{"rev-parse", "refs/stash@{3}"},
|
||||
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd", "-m", "New message"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
index: 4,
|
||||
message: "",
|
||||
expectedShaCmd: []string{"rev-parse", "refs/stash@{4}"},
|
||||
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expectedShaCmd, s.shaResult, nil).
|
||||
ExpectGitArgs(s.expectedDropCmd, "", nil).
|
||||
ExpectGitArgs(s.expectedStoreCmd, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
err := instance.Rename(s.index, s.message)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
@@ -1,4 +1,4 @@
|
||||
package loaders
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -18,13 +17,13 @@ import (
|
||||
type WorkingTreeCommands struct {
|
||||
*GitCommon
|
||||
submodule *SubmoduleCommands
|
||||
fileLoader *loaders.FileLoader
|
||||
fileLoader *FileLoader
|
||||
}
|
||||
|
||||
func NewWorkingTreeCommands(
|
||||
gitCommon *GitCommon,
|
||||
submodule *SubmoduleCommands,
|
||||
fileLoader *loaders.FileLoader,
|
||||
fileLoader *FileLoader,
|
||||
) *WorkingTreeCommands {
|
||||
return &WorkingTreeCommands{
|
||||
GitCommon: gitCommon,
|
||||
@@ -90,7 +89,7 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
|
||||
// all files, passing the --no-renames flag and then recursively call the function
|
||||
// again for the before file and after file.
|
||||
|
||||
filesWithoutRenames := self.fileLoader.GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true})
|
||||
filesWithoutRenames := self.fileLoader.GetStatusFiles(GetStatusFileOptions{NoRenames: true})
|
||||
|
||||
var beforeFile *models.File
|
||||
var afterFile *models.File
|
||||
@@ -241,7 +240,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
if cached {
|
||||
cachedArg = " --cached"
|
||||
}
|
||||
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached {
|
||||
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() {
|
||||
trackedArg = "--no-index -- /dev/null"
|
||||
}
|
||||
if plain {
|
||||
|
||||
@@ -2,7 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
@@ -432,7 +432,7 @@ func TestWorkingTreeApplyPatch(t *testing.T) {
|
||||
|
||||
filename := matches[1]
|
||||
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
content, err := os.ReadFile(filename)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "test", string(content))
|
||||
|
||||
@@ -3,7 +3,7 @@ package git_config
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = ioutil.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
|
||||
err := cmd.Run()
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
|
||||
@@ -11,6 +11,7 @@ type Branch struct {
|
||||
Pullables string
|
||||
UpstreamGone bool
|
||||
Head bool
|
||||
DetachedHead bool
|
||||
// if we have a named remote locally this will be the name of that remote e.g.
|
||||
// 'origin' or 'tiwood'. If we don't have the remote locally it'll look like
|
||||
// 'git@github.com:tiwood/lazygit.git'
|
||||
@@ -19,6 +20,9 @@ type Branch struct {
|
||||
}
|
||||
|
||||
func (b *Branch) FullRefName() string {
|
||||
if b.DetachedHead {
|
||||
return b.Name
|
||||
}
|
||||
return "refs/heads/" + b.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ type IFile interface {
|
||||
GetIsTracked() bool
|
||||
GetPath() string
|
||||
GetPreviousPath() string
|
||||
GetIsFile() bool
|
||||
}
|
||||
|
||||
func (f *File) IsRename() bool {
|
||||
@@ -92,6 +93,10 @@ func (f *File) GetPreviousPath() string {
|
||||
return f.PreviousName
|
||||
}
|
||||
|
||||
func (f *File) GetIsFile() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type StatusFields struct {
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
|
||||
@@ -22,6 +22,8 @@ type ICmdObj interface {
|
||||
Run() error
|
||||
// runs the command and returns the output as a string, and an error if any
|
||||
RunWithOutput() (string, error)
|
||||
// runs the command and returns stdout and stderr as a string, and an error if any
|
||||
RunWithOutputs() (string, string, error)
|
||||
// runs the command and runs a callback function on each line of the output. If the callback returns true for the boolean value, we kill the process and return.
|
||||
RunAndProcessLines(onLine func(line string) (bool, error)) error
|
||||
|
||||
@@ -162,6 +164,10 @@ func (self *CmdObj) RunWithOutput() (string, error) {
|
||||
return self.runner.RunWithOutput(self)
|
||||
}
|
||||
|
||||
func (self *CmdObj) RunWithOutputs() (string, string, error) {
|
||||
return self.runner.RunWithOutputs(self)
|
||||
}
|
||||
|
||||
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
type ICmdObjRunner interface {
|
||||
Run(cmdObj ICmdObj) error
|
||||
RunWithOutput(cmdObj ICmdObj) (string, error)
|
||||
RunWithOutputs(cmdObj ICmdObj) (string, string, error)
|
||||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
@@ -76,6 +77,31 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return self.RunWithOutputAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
}
|
||||
|
||||
if cmdObj.GetCredentialStrategy() != NONE {
|
||||
err := self.runWithCredentialHandling(cmdObj)
|
||||
// for now we're not capturing output, just because it would take a little more
|
||||
// effort and there's currently no use case for it. Some commands call RunWithOutputs
|
||||
// but ignore the output, hence why we've got this check here.
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if cmdObj.ShouldStreamOutput() {
|
||||
err := self.runAndStream(cmdObj)
|
||||
// for now we're not capturing output, just because it would take a little more
|
||||
// effort and there's currently no use case for it. Some commands call RunWithOutputs
|
||||
// but ignore the output, hence why we've got this check here.
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return self.RunWithOutputsAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
@@ -90,6 +116,28 @@ func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
self.logCmdObj(cmdObj)
|
||||
}
|
||||
|
||||
var outBuffer, errBuffer bytes.Buffer
|
||||
cmd := cmdObj.GetCmd()
|
||||
cmd.Stdout = &outBuffer
|
||||
cmd.Stderr = &errBuffer
|
||||
err := cmd.Run()
|
||||
|
||||
stdout := outBuffer.String()
|
||||
stderr, err := sanitisedCommandOutput(errBuffer.Bytes(), err)
|
||||
if err != nil {
|
||||
self.log.WithField("command", cmdObj.ToString()).Error(stderr)
|
||||
}
|
||||
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
|
||||
@@ -19,6 +19,7 @@ type OSCommandDeps struct {
|
||||
GetenvFn func(string) string
|
||||
RemoveFileFn func(string) error
|
||||
Cmd *CmdObjBuilder
|
||||
TempDir string
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
@@ -38,6 +39,7 @@ func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
getenvFn: deps.GetenvFn,
|
||||
removeFileFn: deps.RemoveFileFn,
|
||||
guiIO: NewNullGuiIO(utils.NewDummyLog()),
|
||||
tempDir: deps.TempDir,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,11 @@ func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
return output, "", err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,7 +2,6 @@ package oscommands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -45,7 +44,7 @@ func NewNullGuiIO(log *logrus.Entry) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: func(string, bool) {},
|
||||
newCmdWriterFn: func() io.Writer { return ioutil.Discard },
|
||||
newCmdWriterFn: func() io.Writer { return io.Discard },
|
||||
promptForCredentialFn: failPromptFn,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package oscommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -151,7 +151,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
c.Log.Error(err)
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
if b, err := ioutil.ReadAll(stderr); err == nil {
|
||||
if b, err := io.ReadAll(stderr); err == nil {
|
||||
if len(b) > 0 {
|
||||
finalErrors = append(finalErrors, string(b))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package oscommands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -149,7 +148,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFile"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte("hello"), 0o600); err != nil {
|
||||
if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
@@ -160,7 +159,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
{
|
||||
filepath.Join(os.TempDir(), "emptyTestFile"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte(""), 0o600); err != nil {
|
||||
if err := os.WriteFile(path, []byte(""), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
@@ -171,7 +170,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
{
|
||||
filepath.Join(os.TempDir(), "testFileWithNewline"),
|
||||
func(path string) {
|
||||
if err := ioutil.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
|
||||
if err := os.WriteFile(path, []byte("hello\n"), 0o600); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
@@ -187,7 +186,7 @@ func TestOSCommandAppendLineToFile(t *testing.T) {
|
||||
if err := osCommand.AppendLineToFile(s.path, "world"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f, err := ioutil.ReadFile(s.path)
|
||||
f, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -152,7 +151,7 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
|
||||
file.Close()
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -243,7 +242,7 @@ func (c *AppConfig) SaveAppState() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath, marshalledAppState, 0o644)
|
||||
err = os.WriteFile(filepath, marshalledAppState, 0o644)
|
||||
if err != nil && os.IsPermission(err) {
|
||||
// apparently when people have read-only permissions they prefer us to fail silently
|
||||
return nil
|
||||
@@ -263,7 +262,7 @@ func loadAppState() (*AppState, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appStateBytes, err := ioutil.ReadFile(filepath)
|
||||
appStateBytes, err := os.ReadFile(filepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ type GuiConfig struct {
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
LightTheme bool `yaml:"lightTheme"`
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
@@ -265,7 +264,8 @@ type KeybindingCommitsConfig struct {
|
||||
}
|
||||
|
||||
type KeybindingStashConfig struct {
|
||||
PopStash string `yaml:"popStash"`
|
||||
PopStash string `yaml:"popStash"`
|
||||
RenameStash string `yaml:"renameStash"`
|
||||
}
|
||||
|
||||
type KeybindingCommitFilesConfig struct {
|
||||
@@ -314,6 +314,8 @@ type CustomCommand struct {
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
Key string `yaml:"key"`
|
||||
|
||||
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
|
||||
@@ -355,9 +357,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
Language: "auto",
|
||||
TimeFormat: time.RFC822,
|
||||
Theme: ThemeConfig{
|
||||
LightTheme: false,
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
InactiveBorderColor: []string{"white"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
@@ -545,7 +546,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
ViewBisectOptions: "b",
|
||||
},
|
||||
Stash: KeybindingStashConfig{
|
||||
PopStash: "g",
|
||||
PopStash: "g",
|
||||
RenameStash: "r",
|
||||
},
|
||||
CommitFiles: KeybindingCommitFilesConfig{
|
||||
CheckoutCommitFile: "c",
|
||||
|
||||
@@ -82,6 +82,10 @@ func (m *statusManager) getStatusString() string {
|
||||
return topStatus.message
|
||||
}
|
||||
|
||||
func (m *statusManager) showStatus() bool {
|
||||
return len(m.statuses) > 0
|
||||
}
|
||||
|
||||
func (gui *Gui) toast(message string) {
|
||||
gui.statusManager.addToastStatus(message)
|
||||
|
||||
|
||||
+19
-23
@@ -1,7 +1,7 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/boxlayout"
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -31,7 +31,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
|
||||
extrasWindowSize := gui.getExtrasWindowSize(height)
|
||||
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || (gui.State.Searching.isSearching || gui.isAnyModeActive())
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@@ -159,30 +159,26 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
}
|
||||
}
|
||||
|
||||
result := []*boxlayout.Box{}
|
||||
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
||||
optionsBox := &boxlayout.Box{Window: "options"}
|
||||
|
||||
if len(appStatus) > 0 {
|
||||
result = append(result,
|
||||
&boxlayout.Box{
|
||||
Window: "appStatus",
|
||||
Size: runewidth.StringWidth(appStatus) + runewidth.StringWidth(INFO_SECTION_PADDING),
|
||||
},
|
||||
)
|
||||
if !gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
optionsBox.Weight = 0
|
||||
appStatusBox.Weight = 1
|
||||
} else {
|
||||
optionsBox.Weight = 1
|
||||
appStatusBox.Size = runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(appStatus)
|
||||
}
|
||||
|
||||
result = append(result,
|
||||
[]*boxlayout.Box{
|
||||
{
|
||||
Window: "options",
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
|
||||
},
|
||||
}...,
|
||||
)
|
||||
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
||||
|
||||
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
|
||||
result = append(result, &boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
Size: runewidth.StringWidth(INFO_SECTION_PADDING) + runewidth.StringWidth(utils.Decolorise(informationStr)),
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
package boxlayout
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArrangeWindows(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
root *Box
|
||||
x0 int
|
||||
y0 int
|
||||
width int
|
||||
height int
|
||||
test func(result map[string]Dimensions)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Empty box",
|
||||
root: &Box{},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(t, result, map[string]Dimensions{})
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static and dynamic panel",
|
||||
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic": {X0: 0, X1: 9, Y0: 1, Y1: 9},
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static and two dynamic panels",
|
||||
root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 0},
|
||||
"dynamic1": {X0: 0, X1: 9, Y0: 1, Y1: 3},
|
||||
"dynamic2": {X0: 0, X1: 9, Y0: 4, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 0, Y0: 0, Y1: 9},
|
||||
"dynamic1": {X0: 1, X1: 3, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 4, X1: 9, Y0: 0, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction only on wide boxes with narrow box",
|
||||
root: &Box{ConditionalDirection: func(width int, height int) Direction {
|
||||
if width > 4 {
|
||||
return COLUMN
|
||||
} else {
|
||||
return ROW
|
||||
}
|
||||
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 4,
|
||||
height: 4,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 1},
|
||||
"dynamic2": {X0: 0, X1: 3, Y0: 2, Y1: 3},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with COLUMN direction only on wide boxes with wide box",
|
||||
root: &Box{ConditionalDirection: func(width int, height int) Direction {
|
||||
if width > 4 {
|
||||
return COLUMN
|
||||
} else {
|
||||
return ROW
|
||||
}
|
||||
}, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}},
|
||||
// 5 / 2 = 2 remainder 1. That remainder goes to the first box.
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 5,
|
||||
height: 5,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 2, Y0: 0, Y1: 4},
|
||||
"dynamic2": {X0: 3, X1: 4, Y0: 0, Y1: 4},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with conditional children where box is wide",
|
||||
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
|
||||
if width > 4 {
|
||||
return []*Box{{Window: "wide", Weight: 1}}
|
||||
} else {
|
||||
return []*Box{{Window: "narrow", Weight: 1}}
|
||||
}
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 5,
|
||||
height: 5,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"wide": {X0: 0, X1: 4, Y0: 0, Y1: 4},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with conditional children where box is narrow",
|
||||
root: &Box{ConditionalChildren: func(width int, height int) []*Box {
|
||||
if width > 4 {
|
||||
return []*Box{{Window: "wide", Weight: 1}}
|
||||
} else {
|
||||
return []*Box{{Window: "narrow", Weight: 1}}
|
||||
}
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 4,
|
||||
height: 4,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"narrow": {X0: 0, X1: 3, Y0: 0, Y1: 3},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with static child with size too large",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 11, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 9, Y0: 0, Y1: 9},
|
||||
// not sure if X0: 10, X1: 9 makes any sense, but testing this in the
|
||||
// actual GUI it seems harmless
|
||||
"dynamic1": {X0: 10, X1: 9, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9},
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 10 total space minus 2 from the status box leaves us with 8.
|
||||
// Total weight is 3, 8 / 3 = 2 with 2 remainder.
|
||||
// We want to end up with 2, 3, 5 (one unit from remainder to each dynamic box)
|
||||
testName: "Distributing remainder across weighted boxes",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Size: 2, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"static": {X0: 0, X1: 1, Y0: 0, Y1: 9}, // 2
|
||||
"dynamic1": {X0: 2, X1: 4, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic2": {X0: 5, X1: 9, Y0: 0, Y1: 9}, // 5
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 9 total space.
|
||||
// total weight is 5, 9 / 5 = 1 with 4 remainder
|
||||
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
|
||||
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
|
||||
testName: "Distributing remainder across weighted boxes 2",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 2, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}, {Weight: 1, Window: "dynamic3"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
|
||||
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// 9 total space.
|
||||
// total weight is 5, 9 / 5 = 1 with 4 remainder
|
||||
// we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last.
|
||||
// Reason being that we just give units to each box evenly and consider weight in subsequent passes.
|
||||
testName: "Distributing remainder across weighted boxes with unnormalized weights",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 4, Window: "dynamic1"}, {Weight: 4, Window: "dynamic2"}, {Weight: 2, Window: "dynamic3"}}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4
|
||||
"dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Another distribution test",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{
|
||||
{Weight: 3, Window: "dynamic1"},
|
||||
{Weight: 1, Window: "dynamic2"},
|
||||
{Weight: 1, Window: "dynamic3"},
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 9,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 4, Y0: 0, Y1: 9}, // 5
|
||||
"dynamic2": {X0: 5, X1: 6, Y0: 0, Y1: 9}, // 2
|
||||
"dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Box with zero weight",
|
||||
root: &Box{Direction: COLUMN, Children: []*Box{
|
||||
{Weight: 1, Window: "dynamic1"},
|
||||
{Weight: 0, Window: "dynamic2"},
|
||||
}},
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
width: 10,
|
||||
height: 10,
|
||||
test: func(result map[string]Dimensions) {
|
||||
assert.EqualValues(
|
||||
t,
|
||||
result,
|
||||
map[string]Dimensions{
|
||||
"dynamic1": {X0: 0, X1: 9, Y0: 0, Y1: 9},
|
||||
"dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9}, // when X0 > X1, we will hide the window
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(ArrangeWindows(s.root, s.x0, s.y0, s.width, s.height))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeWeights(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
input []int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
testName: "empty",
|
||||
input: []int{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
testName: "one item of value 1",
|
||||
input: []int{1},
|
||||
expected: []int{1},
|
||||
},
|
||||
{
|
||||
testName: "one item of value greater than 1",
|
||||
input: []int{2},
|
||||
expected: []int{1},
|
||||
},
|
||||
{
|
||||
testName: "slice contains 1",
|
||||
input: []int{2, 1},
|
||||
expected: []int{2, 1},
|
||||
},
|
||||
{
|
||||
testName: "slice contains 2 and 2",
|
||||
input: []int{2, 2},
|
||||
expected: []int{1, 1},
|
||||
},
|
||||
{
|
||||
testName: "no common multiple",
|
||||
input: []int{2, 3},
|
||||
expected: []int{2, 3},
|
||||
},
|
||||
{
|
||||
testName: "complex case",
|
||||
input: []int{10, 10, 20},
|
||||
expected: []int{1, 1, 2},
|
||||
},
|
||||
{
|
||||
testName: "when a zero weight is included it is ignored",
|
||||
input: []int{10, 10, 20, 0},
|
||||
expected: []int{1, 1, 2, 0},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, normalizeWeights(s.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,7 @@ func (gui *Gui) createPopupPanel(opts types.CreatePopupPanelOpts) error {
|
||||
textArea := confirmationView.TextArea
|
||||
textArea.Clear()
|
||||
textArea.TypeString(opts.Prompt)
|
||||
gui.resizeConfirmationPanel()
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
|
||||
|
||||
@@ -255,7 +255,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.git.Branch.New(newBranchName, branch.Name); err != nil {
|
||||
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
||||
return self.helpers.Refs.NewBranch(selectedBranch.RefName(), selectedBranch.RefName(), "")
|
||||
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
|
||||
|
||||
@@ -758,18 +758,31 @@ func (self *FilesController) createStashMenu() error {
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChanges,
|
||||
OnPress: func() error {
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges, self.c.Tr.NoFilesToStash)
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChangesKeepIndex,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
// if there are no staged files it behaves the same as Stash.Save
|
||||
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex, self.c.Tr.NoFilesToStash)
|
||||
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
|
||||
},
|
||||
Key: 'i',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
|
||||
OnPress: func() error {
|
||||
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
|
||||
},
|
||||
Key: 'U',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashStagedChanges,
|
||||
OnPress: func() error {
|
||||
@@ -777,18 +790,21 @@ func (self *FilesController) createStashMenu() error {
|
||||
if !self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges, self.c.Tr.NoTrackedStagedFilesStash)
|
||||
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
if self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges, self.c.Tr.NoFilesToStash)
|
||||
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
}
|
||||
// ordinary stash
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges, self.c.Tr.NoFilesToStash)
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
@@ -797,7 +813,7 @@ func (self *FilesController) createStashMenu() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) stash() error {
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges, self.c.Tr.NoTrackedStagedFilesStash)
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
}
|
||||
|
||||
func (self *FilesController) createResetToUpstreamMenu() error {
|
||||
@@ -825,11 +841,7 @@ func (self *FilesController) toggleTreeView() error {
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string, errorMsg string) error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(errorMsg)
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string) error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.StashChanges,
|
||||
HandleConfirm: func(stashComment string) error {
|
||||
|
||||
@@ -122,10 +122,6 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.contexts.LocalCommits.SetLimitCommits(true)
|
||||
|
||||
if err := self.c.PushContext(self.contexts.LocalCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -192,7 +192,7 @@ func (self *MergeConflictsController) HandleUndo() error {
|
||||
|
||||
self.c.LogAction("Restoring file to previous state")
|
||||
self.c.LogCommand("Undoing last conflict resolution", false)
|
||||
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
|
||||
if err := os.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.S
|
||||
self.c.LogAction("Resolve merge conflict")
|
||||
self.c.LogCommand(logStr, false)
|
||||
state.PushContent(content)
|
||||
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
|
||||
return true, os.WriteFile(state.GetPath(), []byte(content), 0o644)
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) onLastConflictResolved() error {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type StashController struct {
|
||||
@@ -44,6 +45,11 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
Handler: self.checkSelected(self.handleNewBranchOffStashEntry),
|
||||
Description: self.c.Tr.LcNewBranch,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Stash.RenameStash),
|
||||
Handler: self.checkSelected(self.handleRenameStashEntry),
|
||||
Description: self.c.Tr.LcRenameStash,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@@ -139,3 +145,27 @@ func (self *StashController) postStashRefresh() error {
|
||||
func (self *StashController) handleNewBranchOffStashEntry(stashEntry *models.StashEntry) error {
|
||||
return self.helpers.Refs.NewBranch(stashEntry.RefName(), stashEntry.Description(), "")
|
||||
}
|
||||
|
||||
func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntry) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.RenameStashPrompt,
|
||||
map[string]string{
|
||||
"stashName": stashEntry.RefName(),
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: message,
|
||||
InitialContent: stashEntry.Name,
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RenameStash)
|
||||
err := self.git.Stash.Rename(stashEntry.Index, response)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.context().SetSelectedLineIdx(0) // Select the renamed stash
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -57,8 +57,8 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
|
||||
}
|
||||
|
||||
// need to populate my sub commits
|
||||
commits, err := self.git.Loaders.Commits.GetCommits(
|
||||
loaders.GetCommitsOptions{
|
||||
commits, err := self.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: true,
|
||||
FilterPath: self.modes.Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
|
||||
+12
-2
@@ -22,9 +22,13 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
|
||||
textArea.MoveCursorDown()
|
||||
case key == gocui.KeyArrowUp:
|
||||
textArea.MoveCursorUp()
|
||||
case key == gocui.KeyArrowLeft:
|
||||
case key == gocui.KeyArrowLeft && (mod&gocui.ModAlt) != 0:
|
||||
textArea.MoveLeftWord()
|
||||
case key == gocui.KeyArrowLeft || key == gocui.KeyCtrlB:
|
||||
textArea.MoveCursorLeft()
|
||||
case key == gocui.KeyArrowRight:
|
||||
case key == gocui.KeyArrowRight && (mod&gocui.ModAlt) != 0:
|
||||
textArea.MoveRightWord()
|
||||
case key == gocui.KeyArrowRight || key == gocui.KeyCtrlF:
|
||||
textArea.MoveCursorRight()
|
||||
case key == newlineKey:
|
||||
if allowMultiline {
|
||||
@@ -38,10 +42,16 @@ func (gui *Gui) handleEditorKeypress(textArea *gocui.TextArea, key gocui.Key, ch
|
||||
textArea.ToggleOverwrite()
|
||||
case key == gocui.KeyCtrlU:
|
||||
textArea.DeleteToStartOfLine()
|
||||
case key == gocui.KeyCtrlK:
|
||||
textArea.DeleteToEndOfLine()
|
||||
case key == gocui.KeyCtrlA || key == gocui.KeyHome:
|
||||
textArea.GoToStartOfLine()
|
||||
case key == gocui.KeyCtrlE || key == gocui.KeyEnd:
|
||||
textArea.GoToEndOfLine()
|
||||
case key == gocui.KeyCtrlW:
|
||||
textArea.BackSpaceWord()
|
||||
case key == gocui.KeyCtrlY:
|
||||
textArea.Yank()
|
||||
|
||||
// TODO: see if we need all three of these conditions: maybe the final one is sufficient
|
||||
case ch != 0 && mod == 0 && unicode.IsPrint(ch):
|
||||
|
||||
@@ -89,6 +89,7 @@ func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
view := getView()
|
||||
view.ClearTextArea()
|
||||
view.TextArea.TypeString(text)
|
||||
_ = gui.resizePopupPanel(view, view.TextArea.GetContent())
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ func (self *FileNode) GetIsTracked() bool {
|
||||
return self.SomeFile(func(file *models.File) bool { return file.Tracked })
|
||||
}
|
||||
|
||||
func (self *FileNode) GetIsFile() bool {
|
||||
return self.IsFile()
|
||||
}
|
||||
|
||||
func (self *FileNode) GetPreviousPath() string {
|
||||
if self.File == nil {
|
||||
return ""
|
||||
|
||||
+10
-7
@@ -2,7 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -185,7 +185,7 @@ type GuiRepoState struct {
|
||||
// WindowViewNameMap is a mapping of windows to the current view of that window.
|
||||
// Some views move between windows for example the commitFiles view and when cycling through
|
||||
// side windows we need to know which view to give focus to for a given window
|
||||
WindowViewNameMap map[string]string
|
||||
WindowViewNameMap *utils.ThreadSafeMap[string, string]
|
||||
|
||||
// tells us whether we've set up our views for the current repo. We'll need to
|
||||
// do this whenever we switch back and forth between repos to get the views
|
||||
@@ -494,8 +494,6 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
})
|
||||
deadlock.Opts.Disable = !gui.Debug
|
||||
|
||||
gui.handleTestMode(startArgs.IntegrationTest)
|
||||
|
||||
gui.g.OnSearchEscape = gui.onSearchEscape
|
||||
if err := gui.Config.ReloadUserConfig(); err != nil {
|
||||
return nil
|
||||
@@ -552,6 +550,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
|
||||
gui.c.Log.Info("starting main loop")
|
||||
|
||||
gui.handleTestMode(startArgs.IntegrationTest)
|
||||
|
||||
return gui.g.MainLoop()
|
||||
}
|
||||
|
||||
@@ -656,13 +656,16 @@ func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { //nolint:unpara
|
||||
|
||||
err := subprocess.Run()
|
||||
|
||||
subprocess.Stdout = ioutil.Discard
|
||||
subprocess.Stderr = ioutil.Discard
|
||||
subprocess.Stdout = io.Discard
|
||||
subprocess.Stderr = io.Discard
|
||||
subprocess.Stdin = nil
|
||||
|
||||
if gui.Config.GetUserConfig().PromptToReturnFromSubprocess {
|
||||
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint(gui.Tr.PressEnterToReturn))
|
||||
fmt.Scanln() // wait for enter press
|
||||
|
||||
// scan to buffer to prevent run unintentional operations when TUI resumes.
|
||||
var buffer string
|
||||
fmt.Scanln(&buffer) // wait for enter press
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
@@ -79,3 +79,11 @@ func (self *GuiDriver) MainView() *gocui.View {
|
||||
func (self *GuiDriver) SecondaryView() *gocui.View {
|
||||
return self.gui.secondaryView()
|
||||
}
|
||||
|
||||
func (self *GuiDriver) View(viewName string) *gocui.View {
|
||||
view, err := self.gui.g.View(viewName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -179,6 +179,5 @@ func GetKey(key string) types.Key {
|
||||
} else if runeCount == 1 {
|
||||
return []rune(key)[0]
|
||||
}
|
||||
log.Fatal("Key empty for keybinding: " + strings.ToLower(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
+28
-4
@@ -27,11 +27,35 @@ func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
|
||||
gui.setWindowContext(pair.Main)
|
||||
gui.moveToTopOfWindow(pair.Main)
|
||||
gui.moveMainContextToTop(pair.Main)
|
||||
if pair.Secondary != nil {
|
||||
gui.setWindowContext(pair.Secondary)
|
||||
gui.moveToTopOfWindow(pair.Secondary)
|
||||
gui.moveMainContextToTop(pair.Secondary)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) moveMainContextToTop(context types.Context) {
|
||||
gui.setWindowContext(context)
|
||||
|
||||
view := context.GetView()
|
||||
|
||||
topView := gui.topViewInWindow(context.GetWindowName())
|
||||
if topView == nil {
|
||||
gui.Log.Error("unexpected: topView is nil")
|
||||
return
|
||||
}
|
||||
|
||||
if topView != view {
|
||||
// We need to copy the content to avoid a flicker effect: If we're flicking
|
||||
// through files in the files panel, we use a different view to render the
|
||||
// files vs the directories, and if you select dir A, then file B, then dir
|
||||
// C, you'll briefly see dir A's contents again before the view is updated.
|
||||
// So here we're copying the content from the top window to avoid that
|
||||
// flicker effect.
|
||||
gui.g.CopyContent(topView, view)
|
||||
|
||||
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -53,10 +53,17 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
||||
{
|
||||
isActive: gui.State.Modes.CherryPicking.Active,
|
||||
description: func() string {
|
||||
copiedCount := len(gui.State.Modes.CherryPicking.CherryPickedCommits)
|
||||
text := gui.c.Tr.LcCommitsCopied
|
||||
if copiedCount == 1 {
|
||||
text = gui.c.Tr.LcCommitCopied
|
||||
}
|
||||
|
||||
return gui.withResetButton(
|
||||
fmt.Sprintf(
|
||||
"%d commits copied",
|
||||
len(gui.State.Modes.CherryPicking.CherryPickedCommits),
|
||||
"%d %s",
|
||||
copiedCount,
|
||||
text,
|
||||
),
|
||||
style.FgCyan,
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ func (gui *Gui) handleCreateOptionsMenu() error {
|
||||
OpensMenu: binding.OpensMenu,
|
||||
Label: binding.Description,
|
||||
OnPress: func() error {
|
||||
if binding.Key == nil {
|
||||
if binding.Handler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
COMMIT_ICON = "\ufc16" // ﰖ
|
||||
MERGE_COMMIT_ICON = "\ufb2c" // שּׁ
|
||||
DEFAULT_REMOTE_ICON = "\uf7a1" //
|
||||
STASH_ICON = "\uf01c" //
|
||||
)
|
||||
|
||||
type remoteIcon struct {
|
||||
@@ -28,7 +29,7 @@ var remoteIcons = []remoteIcon{
|
||||
}
|
||||
|
||||
func IconForBranch(branch *models.Branch) string {
|
||||
if branch.DisplayName != "" {
|
||||
if branch.DetachedHead {
|
||||
return DETACHED_HEAD_ICON
|
||||
}
|
||||
return BRANCH_ICON
|
||||
@@ -59,3 +60,7 @@ func IconForRemote(remote *models.Remote) string {
|
||||
}
|
||||
return DEFAULT_REMOTE_ICON
|
||||
}
|
||||
|
||||
func IconForStash(stash *models.StashEntry) string {
|
||||
return STASH_ICON
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package presentation
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
@@ -19,5 +20,11 @@ func getStashEntryDisplayStrings(s *models.StashEntry, diffed bool) []string {
|
||||
if diffed {
|
||||
textStyle = theme.DiffTerminalColor
|
||||
}
|
||||
return []string{textStyle.Sprint(s.Name)}
|
||||
|
||||
res := make([]string, 0, 2)
|
||||
if icons.IsIconEnabled() {
|
||||
res = append(res, textStyle.Sprint(icons.IconForStash(s)))
|
||||
}
|
||||
res = append(res, textStyle.Sprint(s.Name))
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -20,7 +19,7 @@ import (
|
||||
|
||||
func (gui *Gui) getCurrentBranch(path string) string {
|
||||
readHeadFile := func(path string) (string, error) {
|
||||
headFile, err := ioutil.ReadFile(filepath.Join(path, "HEAD"))
|
||||
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
|
||||
if err == nil {
|
||||
content := strings.TrimSpace(string(headFile))
|
||||
refsPrefix := "ref: refs/heads/"
|
||||
@@ -47,7 +46,7 @@ func (gui *Gui) getCurrentBranch(path string) string {
|
||||
}
|
||||
} else {
|
||||
// worktree
|
||||
if worktreeGitDir, err := ioutil.ReadFile(gitDirPath); err == nil {
|
||||
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
|
||||
content := strings.TrimSpace(string(worktreeGitDir))
|
||||
worktreePath := strings.TrimPrefix(content, "gitdir: ")
|
||||
if branch, err := readHeadFile(worktreePath); err == nil {
|
||||
|
||||
+13
-13
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -223,8 +223,8 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
gui.Mutexes.LocalCommitsMutex.Lock()
|
||||
defer gui.Mutexes.LocalCommitsMutex.Unlock()
|
||||
|
||||
commits, err := gui.git.Loaders.Commits.GetCommits(
|
||||
loaders.GetCommitsOptions{
|
||||
commits, err := gui.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: gui.State.Contexts.LocalCommits.GetLimitCommits(),
|
||||
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
||||
IncludeRebaseCommits: true,
|
||||
@@ -245,7 +245,7 @@ func (gui *Gui) refreshCommitFilesContext() error {
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
files, err := gui.git.Loaders.CommitFiles.GetFilesInDiff(from, to, reverse)
|
||||
files, err := gui.git.Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func (gui *Gui) refreshRebaseCommits() error {
|
||||
gui.Mutexes.LocalCommitsMutex.Lock()
|
||||
defer gui.Mutexes.LocalCommitsMutex.Unlock()
|
||||
|
||||
updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Model.Commits)
|
||||
updatedCommits, err := gui.git.Loaders.CommitLoader.MergeRebasingCommits(gui.State.Model.Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -269,7 +269,7 @@ func (gui *Gui) refreshRebaseCommits() error {
|
||||
}
|
||||
|
||||
func (self *Gui) refreshTags() error {
|
||||
tags, err := self.git.Loaders.Tags.GetTags()
|
||||
tags, err := self.git.Loaders.TagLoader.GetTags()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@@ -300,13 +300,13 @@ func (gui *Gui) refreshBranches() {
|
||||
// which allows us to order them correctly. So if we're filtering we'll just
|
||||
// manually load all the reflog commits here
|
||||
var err error
|
||||
reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "")
|
||||
reflogCommits, _, err = gui.git.Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
|
||||
if err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
branches, err := gui.git.Loaders.Branches.Load(reflogCommits)
|
||||
branches, err := gui.git.Loaders.BranchLoader.Load(reflogCommits)
|
||||
if err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
@@ -406,8 +406,8 @@ func (gui *Gui) refreshStateFiles() error {
|
||||
}
|
||||
}
|
||||
|
||||
files := gui.git.Loaders.Files.
|
||||
GetStatusFiles(loaders.GetStatusFileOptions{})
|
||||
files := gui.git.Loaders.FileLoader.
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{})
|
||||
|
||||
conflictFileCount := 0
|
||||
for _, file := range files {
|
||||
@@ -463,7 +463,7 @@ func (gui *Gui) refreshReflogCommits() error {
|
||||
}
|
||||
|
||||
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
|
||||
commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits.
|
||||
commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommitLoader.
|
||||
GetReflogCommits(lastReflogCommit, filterPath)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
@@ -495,7 +495,7 @@ func (gui *Gui) refreshReflogCommits() error {
|
||||
func (gui *Gui) refreshRemotes() error {
|
||||
prevSelectedRemote := gui.State.Contexts.Remotes.GetSelected()
|
||||
|
||||
remotes, err := gui.git.Loaders.Remotes.GetRemotes()
|
||||
remotes, err := gui.git.Loaders.RemoteLoader.GetRemotes()
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
@@ -525,7 +525,7 @@ func (gui *Gui) refreshRemotes() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStashEntries() error {
|
||||
gui.State.Model.StashEntries = gui.git.Loaders.Stash.
|
||||
gui.State.Model.StashEntries = gui.git.Loaders.StashLoader.
|
||||
GetStashEntries(gui.State.Modes.Filtering.GetPath())
|
||||
|
||||
return gui.postRefreshUpdate(gui.State.Contexts.Stash)
|
||||
|
||||
@@ -2,6 +2,7 @@ package custom_commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
@@ -45,8 +46,9 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
|
||||
return func() error {
|
||||
sessionState := self.sessionStateLoader.call()
|
||||
promptResponses := make([]string, len(customCommand.Prompts))
|
||||
form := make(map[string]string)
|
||||
|
||||
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses) }
|
||||
f := func() error { return self.finalHandler(customCommand, sessionState, promptResponses, form) }
|
||||
|
||||
// if we have prompts we'll recursively wrap our confirm handlers with more prompts
|
||||
// until we reach the actual command
|
||||
@@ -60,10 +62,11 @@ func (self *HandlerCreator) call(customCommand config.CustomCommand) func() erro
|
||||
|
||||
wrappedF := func(response string) error {
|
||||
promptResponses[idx] = response
|
||||
form[prompt.Key] = response
|
||||
return g()
|
||||
}
|
||||
|
||||
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
|
||||
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
|
||||
resolvedPrompt, err := self.resolver.resolvePrompt(&prompt, resolveTemplate)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
@@ -154,19 +157,25 @@ func (self *HandlerCreator) menuPromptFromCommand(prompt *config.CustomCommandPr
|
||||
type CustomCommandObjects struct {
|
||||
*SessionState
|
||||
PromptResponses []string
|
||||
Form map[string]string
|
||||
}
|
||||
|
||||
func (self *HandlerCreator) getResolveTemplateFn(promptResponses []string, sessionState *SessionState) func(string) (string, error) {
|
||||
func (self *HandlerCreator) getResolveTemplateFn(form map[string]string, promptResponses []string, sessionState *SessionState) func(string) (string, error) {
|
||||
objects := CustomCommandObjects{
|
||||
SessionState: sessionState,
|
||||
PromptResponses: promptResponses,
|
||||
Form: form,
|
||||
}
|
||||
|
||||
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects) }
|
||||
funcs := template.FuncMap{
|
||||
"quote": self.os.Quote,
|
||||
}
|
||||
|
||||
return func(templateStr string) (string, error) { return utils.ResolveTemplate(templateStr, objects, funcs) }
|
||||
}
|
||||
|
||||
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string) error {
|
||||
resolveTemplate := self.getResolveTemplateFn(promptResponses, sessionState)
|
||||
func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, sessionState *SessionState, promptResponses []string, form map[string]string) error {
|
||||
resolveTemplate := self.getResolveTemplateFn(form, promptResponses, sessionState)
|
||||
cmdStr, err := resolveTemplate(customCommand.Command)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
)
|
||||
@@ -110,17 +107,3 @@ type CustomCommandObject struct {
|
||||
PromptResponses []string
|
||||
Form map[string]string
|
||||
}
|
||||
|
||||
func ResolveTemplate(templateStr string, object interface{}) (string, error) {
|
||||
tmpl, err := template.New("template").Parse(templateStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, object); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ var (
|
||||
BgBlue = FromBasicBg(color.BgBlue)
|
||||
BgMagenta = FromBasicBg(color.BgMagenta)
|
||||
BgCyan = FromBasicBg(color.BgCyan)
|
||||
BgDefault = FromBasicBg(color.BgDefault)
|
||||
|
||||
// will not print any colour escape codes, including the reset escape code
|
||||
Nothing = New()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gookit/color"
|
||||
)
|
||||
|
||||
@@ -25,6 +27,23 @@ import (
|
||||
// a string, we derive it when a new TextStyle is created and store it in the
|
||||
// `style` field.
|
||||
|
||||
// See https://github.com/xtermjs/xterm.js/issues/4238
|
||||
// VSCode is soon to fix this in an upcoming update.
|
||||
// Once that's done, we can scrap the HIDE_UNDERSCORES variable
|
||||
var (
|
||||
underscoreEnvChecked bool
|
||||
hideUnderscores bool
|
||||
)
|
||||
|
||||
func hideUnderScores() bool {
|
||||
if !underscoreEnvChecked {
|
||||
hideUnderscores = os.Getenv("TERM_PROGRAM") == "vscode"
|
||||
underscoreEnvChecked = true
|
||||
}
|
||||
|
||||
return hideUnderscores
|
||||
}
|
||||
|
||||
type TextStyle struct {
|
||||
fg *Color
|
||||
bg *Color
|
||||
@@ -64,6 +83,10 @@ func (b TextStyle) SetBold() TextStyle {
|
||||
}
|
||||
|
||||
func (b TextStyle) SetUnderline() TextStyle {
|
||||
if hideUnderScores() {
|
||||
return b
|
||||
}
|
||||
|
||||
b.decoration.SetUnderline()
|
||||
b.Style = b.deriveStyle()
|
||||
return b
|
||||
|
||||
@@ -2,7 +2,6 @@ package gui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -42,7 +41,7 @@ func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) {
|
||||
if Replaying() {
|
||||
gui.g.RecordingConfig = gocui.RecordingConfig{
|
||||
Speed: GetRecordingSpeed(),
|
||||
Leeway: 100,
|
||||
Leeway: 1000,
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -93,7 +92,7 @@ func GetRecordingSpeed() float64 {
|
||||
func LoadRecording() (*gocui.Recording, error) {
|
||||
path := os.Getenv("REPLAY_EVENTS_FROM")
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,5 +119,5 @@ func SaveRecording(recording *gocui.Recording) error {
|
||||
|
||||
path := recordEventsTo()
|
||||
|
||||
return ioutil.WriteFile(path, jsonEvents, 0o600)
|
||||
return os.WriteFile(path, jsonEvents, 0o600)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,11 @@ func (gui *Gui) resizeConfirmationPanel() {
|
||||
}
|
||||
panelWidth := gui.getConfirmationPanelWidth()
|
||||
prompt := gui.Views.Confirmation.Buffer()
|
||||
wrap := !gui.Views.Confirmation.Editable
|
||||
wrap := true
|
||||
if gui.Views.Confirmation.Editable {
|
||||
prompt = gui.Views.Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
|
||||
+34
-14
@@ -3,8 +3,10 @@ package gui
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -14,18 +16,18 @@ import (
|
||||
// space. Right now most windows are 1:1 with views, except for commitFiles which
|
||||
// is a view that moves between windows
|
||||
|
||||
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
|
||||
result := map[string]string{}
|
||||
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
|
||||
result := utils.NewThreadSafeMap[string, string]()
|
||||
|
||||
for _, context := range contextTree.Flatten() {
|
||||
result[context.GetWindowName()] = context.GetViewName()
|
||||
result.Set(context.GetWindowName(), context.GetViewName())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) getViewNameForWindow(window string) string {
|
||||
viewName, ok := gui.State.WindowViewNameMap[window]
|
||||
viewName, ok := gui.State.WindowViewNameMap.Get(window)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Viewname not found for window: %s", window))
|
||||
}
|
||||
@@ -50,7 +52,7 @@ func (gui *Gui) setWindowContext(c types.Context) {
|
||||
gui.resetWindowContext(c)
|
||||
}
|
||||
|
||||
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
|
||||
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
|
||||
}
|
||||
|
||||
func (gui *Gui) currentWindow() string {
|
||||
@@ -59,39 +61,57 @@ func (gui *Gui) currentWindow() string {
|
||||
|
||||
// assumes the context's windowName has been set to the new window if necessary
|
||||
func (gui *Gui) resetWindowContext(c types.Context) {
|
||||
for windowName, viewName := range gui.State.WindowViewNameMap {
|
||||
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
|
||||
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
|
||||
for _, context := range gui.State.Contexts.Flatten() {
|
||||
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
|
||||
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
|
||||
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) moveToTopOfWindow(context types.Context) {
|
||||
// moves given context's view to the top of the window and returns
|
||||
// true if the view was not already on top.
|
||||
func (gui *Gui) moveToTopOfWindow(context types.Context) bool {
|
||||
view := context.GetView()
|
||||
if view == nil {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
window := context.GetWindowName()
|
||||
|
||||
topView := gui.topViewInWindow(window)
|
||||
|
||||
if view.Name() == topView.Name() {
|
||||
return false
|
||||
} else {
|
||||
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) topViewInWindow(windowName string) *gocui.View {
|
||||
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
|
||||
viewNamesInWindow := gui.viewNamesInWindow(window)
|
||||
viewNamesInWindow := gui.viewNamesInWindow(windowName)
|
||||
|
||||
// The views list is ordered highest-last, so we're grabbing the last view of the window
|
||||
topView := view
|
||||
var topView *gocui.View
|
||||
for _, currentView := range gui.g.Views() {
|
||||
if lo.Contains(viewNamesInWindow, currentView.Name()) {
|
||||
topView = currentView
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
return topView
|
||||
}
|
||||
|
||||
func (gui *Gui) viewNamesInWindow(windowName string) []string {
|
||||
|
||||
@@ -139,6 +139,8 @@ func chineseTranslationSet() TranslationSet {
|
||||
SureApplyStashEntry: "您确定要应用此贮藏条目?",
|
||||
NoTrackedStagedFilesStash: "没有可以贮藏的已跟踪/暂存文件",
|
||||
StashChanges: "贮藏更改",
|
||||
LcRenameStash: "rename stash",
|
||||
RenameStashPrompt: "Rename stash: {{.stashName}}",
|
||||
OpenConfig: "打开配置文件",
|
||||
EditConfig: "编辑配置文件",
|
||||
ForcePush: "强制推送",
|
||||
@@ -530,6 +532,7 @@ func chineseTranslationSet() TranslationSet {
|
||||
UpdateRemote: "更新远程",
|
||||
ApplyPatch: "应用补丁",
|
||||
Stash: "贮藏 (Stash)",
|
||||
RenameStash: "Rename stash",
|
||||
RemoveSubmodule: "删除子模块",
|
||||
ResetSubmodule: "重置子模块",
|
||||
AddSubmodule: "添加子模块",
|
||||
|
||||
@@ -105,6 +105,8 @@ func dutchTranslationSet() TranslationSet {
|
||||
SureApplyStashEntry: "Weet je zeker dat je deze stash entry wil toepassen?",
|
||||
NoTrackedStagedFilesStash: "Je hebt geen tracked/staged bestanden om te laten stashen",
|
||||
StashChanges: "Stash veranderingen",
|
||||
LcRenameStash: "rename stash",
|
||||
RenameStashPrompt: "Rename stash: {{.stashName}}",
|
||||
NoChangedFiles: "Geen veranderde bestanden",
|
||||
OpenConfig: "open config bestand",
|
||||
EditConfig: "verander config bestand",
|
||||
|
||||
@@ -128,6 +128,8 @@ type TranslationSet struct {
|
||||
NoTrackedStagedFilesStash string
|
||||
NoFilesToStash string
|
||||
StashChanges string
|
||||
LcRenameStash string
|
||||
RenameStashPrompt string
|
||||
OpenConfig string
|
||||
EditConfig string
|
||||
ForcePush string
|
||||
@@ -293,6 +295,7 @@ type TranslationSet struct {
|
||||
LcStashStagedChanges string
|
||||
LcStashAllChangesKeepIndex string
|
||||
LcStashUnstagedChanges string
|
||||
LcStashIncludeUntrackedChanges string
|
||||
LcStashOptions string
|
||||
NotARepository string
|
||||
LcJump string
|
||||
@@ -507,6 +510,8 @@ type TranslationSet struct {
|
||||
EmptyOutput string
|
||||
Patch string
|
||||
CustomPatch string
|
||||
LcCommitsCopied string
|
||||
LcCommitCopied string
|
||||
Actions Actions
|
||||
Bisect Bisect
|
||||
}
|
||||
@@ -584,6 +589,7 @@ type Actions struct {
|
||||
StashAllChangesKeepIndex string
|
||||
StashStagedChanges string
|
||||
StashUnstagedChanges string
|
||||
StashIncludeUntrackedChanges string
|
||||
GitFlowFinish string
|
||||
GitFlowStart string
|
||||
CopyToClipboard string
|
||||
@@ -599,6 +605,7 @@ type Actions struct {
|
||||
UpdateRemote string
|
||||
ApplyPatch string
|
||||
Stash string
|
||||
RenameStash string
|
||||
RemoveSubmodule string
|
||||
ResetSubmodule string
|
||||
AddSubmodule string
|
||||
@@ -767,6 +774,8 @@ func EnglishTranslationSet() TranslationSet {
|
||||
NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
|
||||
NoFilesToStash: "You have no files to stash",
|
||||
StashChanges: "Stash changes",
|
||||
LcRenameStash: "rename stash",
|
||||
RenameStashPrompt: "Rename stash: {{.stashName}}",
|
||||
OpenConfig: "open config file",
|
||||
EditConfig: "edit config file",
|
||||
ForcePush: "Force push",
|
||||
@@ -933,6 +942,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
LcStashStagedChanges: "stash staged changes",
|
||||
LcStashAllChangesKeepIndex: "stash all changes and keep index",
|
||||
LcStashUnstagedChanges: "stash unstaged changes",
|
||||
LcStashIncludeUntrackedChanges: "stash all changes including untracked files",
|
||||
LcStashOptions: "Stash options",
|
||||
NotARepository: "Error: must be run inside a git repository",
|
||||
LcJump: "jump to panel",
|
||||
@@ -1147,6 +1157,8 @@ func EnglishTranslationSet() TranslationSet {
|
||||
EmptyOutput: "<empty output>",
|
||||
Patch: "Patch",
|
||||
CustomPatch: "Custom patch",
|
||||
LcCommitsCopied: "commits copied",
|
||||
LcCommitCopied: "commit copied",
|
||||
Actions: Actions{
|
||||
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
||||
CheckoutCommit: "Checkout commit",
|
||||
@@ -1207,6 +1219,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
StashAllChangesKeepIndex: "Stash all changes and keep index",
|
||||
StashStagedChanges: "Stash staged changes",
|
||||
StashUnstagedChanges: "Stash unstaged changes",
|
||||
StashIncludeUntrackedChanges: "Stash all changes including untracked files",
|
||||
GitFlowFinish: "Git flow finish",
|
||||
GitFlowStart: "Git Flow start",
|
||||
CopyToClipboard: "Copy to clipboard",
|
||||
@@ -1222,6 +1235,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
UpdateRemote: "Update remote",
|
||||
ApplyPatch: "Apply patch",
|
||||
Stash: "Stash",
|
||||
RenameStash: "Rename stash",
|
||||
RemoveSubmodule: "Remove submodule",
|
||||
ResetSubmodule: "Reset submodule",
|
||||
AddSubmodule: "Add submodule",
|
||||
|
||||
@@ -130,6 +130,8 @@ func japaneseTranslationSet() TranslationSet {
|
||||
SureApplyStashEntry: "Stashを適用します。よろしいですか?",
|
||||
// NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
|
||||
StashChanges: "変更をStash",
|
||||
LcRenameStash: "Stashを変更",
|
||||
RenameStashPrompt: "Stash名を変更: {{.stashName}}",
|
||||
OpenConfig: "設定ファイルを開く",
|
||||
EditConfig: "設定ファイルを編集",
|
||||
ForcePush: "Force push",
|
||||
@@ -556,6 +558,7 @@ func japaneseTranslationSet() TranslationSet {
|
||||
UpdateRemote: "リモートを更新",
|
||||
ApplyPatch: "パッチを適用",
|
||||
Stash: "Stash",
|
||||
RenameStash: "Stash名を変更",
|
||||
RemoveSubmodule: "サブモジュールを削除",
|
||||
ResetSubmodule: "サブモジュールをリセット",
|
||||
AddSubmodule: "サブモジュールを追加",
|
||||
|
||||
@@ -131,6 +131,8 @@ func koreanTranslationSet() TranslationSet {
|
||||
SureApplyStashEntry: "정말로 Stash를 적용하시겠습니까?",
|
||||
NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
|
||||
StashChanges: "변경을 Stash",
|
||||
LcRenameStash: "rename stash",
|
||||
RenameStashPrompt: "Rename stash: {{.stashName}}",
|
||||
OpenConfig: "설정 파일 열기",
|
||||
EditConfig: "설정 파일 수정",
|
||||
ForcePush: "강제 푸시",
|
||||
@@ -561,6 +563,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
UpdateRemote: "Update remote",
|
||||
ApplyPatch: "Apply patch",
|
||||
Stash: "Stash",
|
||||
RenameStash: "Rename stash",
|
||||
RemoveSubmodule: "서브모듈 삭제",
|
||||
ResetSubmodule: "서브모듈 Reset",
|
||||
AddSubmodule: "서브모듈 추가",
|
||||
|
||||
@@ -83,6 +83,8 @@ func polishTranslationSet() TranslationSet {
|
||||
SureDropStashEntry: "Jesteś pewny, że chcesz porzucić tę pozycję w schowku?",
|
||||
NoTrackedStagedFilesStash: "Nie masz śledzonych/zatwierdzonych plików do przechowania",
|
||||
StashChanges: "Przechowaj zmiany",
|
||||
LcRenameStash: "rename stash",
|
||||
RenameStashPrompt: "Rename stash: {{.stashName}}",
|
||||
OpenConfig: "otwórz konfigurację",
|
||||
EditConfig: "edytuj konfigurację",
|
||||
ForcePush: "Wymuś wysłanie",
|
||||
|
||||
@@ -11,7 +11,7 @@ go run cmd/integration_test/main.go tui
|
||||
or
|
||||
|
||||
```sh
|
||||
go run cmd/integration_test/main.go cli [--slow] [testname or testpath...]
|
||||
go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...]
|
||||
```
|
||||
|
||||
## Writing tests
|
||||
@@ -49,7 +49,7 @@ If you find yourself doing something frequently in a test, consider making it a
|
||||
|
||||
There are three ways to invoke a test:
|
||||
|
||||
1. go run cmd/integration_test/main.go cli [--slow] [testname or testpath...]
|
||||
1. go run cmd/integration_test/main.go cli [--slow or --sandbox] [testname or testpath...]
|
||||
2. go run cmd/integration_test/main.go tui
|
||||
3. go test pkg/integration/clients/go_test.go
|
||||
|
||||
@@ -61,6 +61,15 @@ The name of a test is based on its path, so the name of the test at `pkg/integra
|
||||
|
||||
You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode.
|
||||
|
||||
### Running tests in VSCode
|
||||
|
||||
If you've opened an integration test file in your editor you can run that file by bringing up the command panel with `cmd+shift+p` and typing 'run task', then selecting the test task you want to run
|
||||
|
||||

|
||||

|
||||
The test will run in a VSCode terminal:
|
||||

|
||||
|
||||
### Snapshots
|
||||
|
||||
At the moment (this is subject to change) each test has a snapshot repo created after running for the first time. These snapshots live in `test/integration_new`, in folders named 'expected' (alongside the 'actual' folders which contain the resulting repo from the last test run). Whenever you run a test, the resultant repo will be compared against the snapshot repo and if they're different, you'll be asked whether you want to update the snapshot. If you want to update a snapshot without being prompted you can pass MODE=update to the test runner.
|
||||
@@ -69,7 +78,7 @@ At the moment (this is subject to change) each test has a snapshot repo created
|
||||
|
||||
Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself.
|
||||
|
||||
To run a test in sandbox mode you can press 's' on a test in the test TUI or pass the env var MODE=sandbox to the test runner.
|
||||
To run a test in sandbox mode you can press 's' on a test in the test TUI or in the test runner pass MODE=sandbox or the --sandbox argument.
|
||||
|
||||
## Migration process
|
||||
|
||||
@@ -84,3 +93,5 @@ go run pkg/integration/deprecated/cmd/tui/main.go
|
||||
```
|
||||
|
||||
The tests in the old format live in test/integration. In the old format, test definitions are co-located with the snapshots. The setup step is done in a `setup.sh` shell script and the `recording.json` file contains the recorded keypresses to be replayed during the test.
|
||||
|
||||
If you have rewritten an integration test under the new pattern, be sure to delete the old integration test directory.
|
||||
|
||||
@@ -23,26 +23,34 @@ import (
|
||||
|
||||
// If invoked directly, you can specify tests to run by passing their names as positional arguments
|
||||
|
||||
func RunCLI(testNames []string, slow bool) {
|
||||
func RunCLI(testNames []string, slow bool, sandbox bool) {
|
||||
keyPressDelay := tryConvert(os.Getenv("KEY_PRESS_DELAY"), 0)
|
||||
if slow {
|
||||
keyPressDelay = SLOW_KEY_PRESS_DELAY
|
||||
}
|
||||
|
||||
var mode components.Mode
|
||||
if sandbox {
|
||||
mode = components.SANDBOX
|
||||
} else {
|
||||
mode = getModeFromEnv()
|
||||
}
|
||||
|
||||
err := components.RunTests(
|
||||
getTestsToRun(testNames),
|
||||
log.Printf,
|
||||
runCmdInTerminal,
|
||||
runAndPrintError,
|
||||
getModeFromEnv(),
|
||||
runAndPrintFatalError,
|
||||
mode,
|
||||
keyPressDelay,
|
||||
1,
|
||||
)
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func runAndPrintError(test *components.IntegrationTest, f func() error) {
|
||||
func runAndPrintFatalError(test *components.IntegrationTest, f func() error) {
|
||||
if err := f(); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user