Compare commits

...

149 Commits

Author SHA1 Message Date
Jesse Duffield
7d86278507 Merge pull request #274 from sascha-andres/master
fix: escape quote character on Linux
2018-09-11 09:07:36 +10:00
Sascha Andres
985196f5aa docs: add comments for new test code 2018-09-10 17:36:59 +02:00
Sascha Andres
9ec5a04cf5 merge: remote-tracking branch 'upstream/master' 2018-09-10 10:33:20 +02:00
Sascha Andres
bb9698810d refactor: move fallback to platform struct
Co-authored-by: Jesse Duffield <jessedduffield@gmail.com>
2018-09-10 10:31:15 +02:00
Jesse Duffield
7f4371ad71 Merge pull request #275 from mjarkk/master
Translated untranslated dutch phrases
2018-09-10 18:15:15 +10:00
Sascha Andres
74144e3892 fix: remove call to fmt.Println 2018-09-10 10:10:10 +02:00
mjarkk
07f87eb7cf Added missing dutch translations 2018-09-10 08:08:53 +02:00
Mark Kopenga
5600da9ee7 Merge pull request #11 from jesseduffield/master
updated to latest master
2018-09-10 07:28:11 +02:00
Sascha Andres
ba0cc20e22 feat: add test cases 2018-09-10 06:51:19 +02:00
Sascha Andres
717913e64c fix: escape quote character on Linux
Co-authored-by: Dawid Dziurla <dawidd0811@gmail.com>

Closes #269
2018-09-10 06:36:15 +02:00
Jesse Duffield
5a0431bb62 Merge pull request #272 from antham/master
Add missing go.sum file
2018-09-10 10:58:22 +10:00
Jesse Duffield
1e357e2362 Merge branch 'master' into master 2018-09-10 10:02:06 +10:00
Jesse Duffield
c3b62a555c Merge pull request #265 from antham/add-tests-part-2
Add tests to pkg/commands/git - Part 2
2018-09-10 10:01:55 +10:00
Jesse Duffield
557461c016 Merge branch 'master' into add-tests-part-2 2018-09-10 09:47:14 +10:00
Jesse Duffield
c2e670104e Merge branch 'master' into master 2018-09-10 09:46:01 +10:00
Jesse Duffield
bea9971f9c Merge pull request #271 from glvr182/feature/ignore-hidden-files
Change the gitignore a bit to cover more
2018-09-10 09:45:29 +10:00
Jesse Duffield
a8fdf1a646 Merge branch 'master' into feature/ignore-hidden-files 2018-09-10 09:45:18 +10:00
Jesse Duffield
f8ab4f4073 Merge pull request #234 from dawidd6/feature/help
WIP: Add menu panel (cheatsheet)
2018-09-10 09:43:28 +10:00
Anthony HAMON
24f15742d0 commands/git : rename variable 2018-09-09 20:08:46 +02:00
Glenn Vriesman
700f8c7e79 REPO: Added TODO entries to the gitignore for jesse 2018-09-09 13:37:38 +02:00
Anthony HAMON
28e8d6e472 add go.sum 2018-09-09 11:13:18 +02:00
Anthony HAMON
6076a75643 commands/git : fix function call 2018-09-09 10:52:34 +02:00
Anthony HAMON
b46e4b4976 commands/git : add several tests, do some cleanup 2018-09-09 10:52:34 +02:00
Anthony HAMON
99eca7b000 commands/git : replace make function 2018-09-09 10:52:34 +02:00
Anthony HAMON
a0faaf6893 commands/git : remove includes function 2018-09-09 10:52:34 +02:00
Anthony HAMON
56ad07ebab commands/git : rename functions 2018-09-09 10:52:34 +02:00
Anthony HAMON
1ecd74c357 commands/git : add tests for GetCommitsToPush 2018-09-09 10:52:34 +02:00
Anthony HAMON
ceab9706cb commands/git : add tests for UpstreamDifferentCount 2018-09-09 10:52:34 +02:00
Anthony HAMON
1cc7e9c02a rewrite to use subtests 2018-09-09 10:52:34 +02:00
Dawid Dziurla
63e400647a shorter english string 2018-09-09 10:47:13 +02:00
Dawid Dziurla
6f7de83bce Merge branch 'master' into feature/help
conflicts resolved
2018-09-09 10:41:01 +02:00
Jesse Duffield
5af03b6820 Merge pull request #264 from antham/master
Add go.mod to support modules landed in go1.11
2018-09-09 11:35:48 +10:00
Glenn Vriesman
a8866b158b REPO: Added and modified some ignore statements 2018-09-08 14:25:33 +02:00
antham
0d7a697e86 add go.mod 2018-09-07 21:35:04 +02:00
Dawid Dziurla
e80371fc6f satisfy golangci 2018-09-07 14:41:01 +02:00
Dawid Dziurla
9cef98f779 ladies and gentlemen...
this is fmt number x+1
2018-09-07 14:23:08 +02:00
Dawid Dziurla
ba6dedfb22 rewrite some of menu panel logic
panel keybindings are now on top and
global keybindings are below separated with empty newline
2018-09-07 14:19:16 +02:00
Jesse Duffield
ba7e6add86 Merge pull request #267 from glvr182/feature/add-effgo-link
Added entry to the contributing guide
2018-09-07 09:30:29 +10:00
Glenn Vriesman
a820189450 Added entry to the contributing guide 2018-09-06 17:31:19 +02:00
Jesse Duffield
ce95e6771a Merge pull request #263 from dawidd6/feature/confirm_quit
Add confirmOnQuit config option
2018-09-06 09:33:06 +10:00
Dawid Dziurla
e9268d1828 add confirmOnQuit config option 2018-09-05 19:56:11 +02:00
Dawid Dziurla
db2e2160a9 change menu keybinding from ? to x 2018-09-05 15:55:24 +02:00
Dawid Dziurla
08395ae76c workaround to include menu keybinding in cheatsheet 2018-09-05 15:45:20 +02:00
Dawid Dziurla
4188786749 update pl translation 2018-09-05 15:20:34 +02:00
Jesse Duffield
cf41338a9f Merge pull request #262 from jesseduffield/feature/50/jesse
Add commit counter using subtitle
2018-09-05 23:09:20 +10:00
Jesse Duffield
a2d40cfbf1 allow users to configure whether the commit length is shown 2018-09-05 23:02:13 +10:00
Dawid Dziurla
34d1648bd3 fmt strikes again 2018-09-05 13:23:06 +02:00
Dawid Dziurla
906f8e252e include global keybindings in menu 2018-09-05 13:16:40 +02:00
Jesse Duffield
986774e5c7 add commit count via gocui subtitle 2018-09-05 20:43:45 +10:00
Dawid Dziurla
98763e98cb initial commit message counter 2018-09-05 11:26:54 +02:00
Jesse Duffield
f777c60ea4 Merge pull request #260 from jesseduffield/hotfix/258-commit-message-newlines
Fix popup panel resizing
2018-09-05 19:15:39 +10:00
Dawid Dziurla
557009e660 help -> menu 2018-09-05 11:12:11 +02:00
Jesse Duffield
af876d2be2 Merge branch 'master' into hotfix/258-commit-message-newlines 2018-09-05 19:11:30 +10:00
Jesse Duffield
422b263df4 fix popup panel resizing 2018-09-05 19:10:46 +10:00
Jesse Duffield
4fc290b101 Update README.md 2018-09-05 18:44:24 +10:00
Jesse Duffield
c1bf1e52b0 Merge pull request #257 from antham/master
Increase circleci compile speed
2018-09-05 17:12:29 +10:00
Anthony HAMON
4d745fa525 update cache path in circleci 2018-09-05 08:55:15 +02:00
Jesse Duffield
f0e19690f5 Merge pull request #254 from jesseduffield/feature/dockerfile-for-alpine
Dockerfile for testing on alpine linux
2018-09-05 14:20:51 +10:00
Jesse Duffield
580c1cbc89 Merge branch 'master' into feature/dockerfile-for-alpine 2018-09-05 11:03:17 +10:00
Dawid Dziurla
e21f739f4f add renderGlobalOptions
render only global options for all panels
2018-09-04 16:07:31 +02:00
Dawid Dziurla
97ad4a1643 delete options 2018-09-04 15:40:29 +02:00
Dawid Dziurla
cbafadd48e move keys slice to guiState struct 2018-09-04 15:29:43 +02:00
Dawid Dziurla
7b84c162f4 set help panel fgcolor to white 2018-09-04 15:25:54 +02:00
Dawid Dziurla
f29c81fb5c add getMaxKeyLength 2018-09-04 15:25:02 +02:00
Jesse Duffield
59003c8bbf Merge pull request #253 from jesseduffield/hotfix/238-xdg-open-2
Update xdg-open command
2018-09-04 22:21:03 +10:00
Jesse Duffield
6c1d133315 dockerfile for testing on alpine linux 2018-09-04 22:19:08 +10:00
Jesse Duffield
33ea093d88 Merge branch 'master' into hotfix/238-xdg-open-2 2018-09-04 20:37:35 +10:00
Jesse Duffield
a81f8b84e3 Merge pull request #245 from antham/master
Rewrite SetupGit in GitCommand
2018-09-04 19:29:21 +10:00
Jesse Duffield
3f68fe42cb update xdg-open command 2018-09-04 19:18:18 +10:00
Anthony HAMON
172cd7c687 fix tests locally 2018-09-04 08:32:43 +02:00
Anthony HAMON
df3e7abd68 use RunCommand 2018-09-04 08:32:40 +02:00
Anthony HAMON
8c67578063 replace fmt with errors 2018-09-04 06:21:58 +02:00
Anthony HAMON
06846ef3ae rename NewApp to Setup 2018-09-04 06:21:58 +02:00
Anthony HAMON
43ad9a81c2 merge setup in function that create a new git command 2018-09-04 06:21:58 +02:00
Anthony HAMON
9f7775df26 pkg/git : remove unused Map function 2018-09-04 06:21:58 +02:00
Anthony HAMON
c1984528c8 pkg/git : add tests for SetupGit 2018-09-04 06:21:58 +02:00
Anthony HAMON
624d63d2fa pkg/git : remove panic in SetupGit method 2018-09-04 06:21:58 +02:00
Dawid Dziurla
67d99a24ea get selected branch from correct panel 2018-09-03 18:45:52 +02:00
Dawid Dziurla
bf8514f5e2 helperize spaces 2018-09-03 18:44:56 +02:00
Dawid Dziurla
93e88ea8fe add missing translated polish strings 2018-09-03 18:34:04 +02:00
Dawid Dziurla
a2073528f4 add missing untranslated dutch strings
@mjarkk
2018-09-03 18:33:38 +02:00
Dawid Dziurla
230a5afa4c remove capitalization of keybindings descriptions 2018-09-03 18:16:54 +02:00
Dawid Dziurla
c49e4dc287 get item position from correct panel 2018-09-03 18:07:38 +02:00
Dawid Dziurla
59f50010b6 apply fmt again 2018-09-03 18:01:07 +02:00
Dawid Dziurla
b5827b7d80 merge conflict effect fix 2018-09-03 17:57:03 +02:00
Dawid Dziurla
36874be45b apply very important fmt 2018-09-03 17:54:06 +02:00
Dawid Dziurla
83b7c60246 add untranslated dutch strings
@mjarkk
2018-09-03 17:54:06 +02:00
Dawid Dziurla
323016aa01 polish translation 2018-09-03 17:54:06 +02:00
Dawid Dziurla
b403b6e46d better english string 2018-09-03 17:54:06 +02:00
Dawid Dziurla
359636c1aa add generate_cheatsheet script
script is generating markdown document with small cheatsheet
in selected language
2018-09-03 17:54:06 +02:00
Dawid Dziurla
1fa55875e2 remove testing content 2018-09-03 17:54:06 +02:00
Dawid Dziurla
5177e458ef use Fprint instead of renderString
renderString is wrapping content
because of that lines are being select wrong
2018-09-03 17:54:06 +02:00
Dawid Dziurla
314c8c279a apply fmt on keybindings 2018-09-03 17:54:06 +02:00
Dawid Dziurla
20073d0293 don't panic
"panic: runtime error: index out of range"

when executing stash pop 'g' from help menu
2018-09-03 17:54:06 +02:00
Dawid Dziurla
90a4cada82 add missing descriptions 2018-09-03 17:54:06 +02:00
Dawid Dziurla
e376de6d1a explicitly delete 'help' view 2018-09-03 17:54:06 +02:00
Dawid Dziurla
265d7e121a use Key if it's a rune, otherwise KeyReadable 2018-09-03 17:54:06 +02:00
Dawid Dziurla
7ec5b6cc30 indent keybindings 2018-09-03 17:54:06 +02:00
Dawid Dziurla
9ceaf5b9a9 move descriptions to i18n 2018-09-03 17:52:05 +02:00
Dawid Dziurla
cc3fa4b79d make '?' key visible on every panel 2018-09-03 17:52:05 +02:00
Dawid Dziurla
653d590157 help panel size from getConfirmationPanelDimensions 2018-09-03 17:52:05 +02:00
Dawid Dziurla
8a01d11202 more error checks 2018-09-03 17:52:05 +02:00
Dawid Dziurla
28a9594ef7 update help panel
- delete scrolling ability
- lines are now selectable
- implemented handler execution when space is pressed
- add example descriptions for status panel keybindings
2018-09-03 17:52:05 +02:00
Dawid Dziurla
77623db1d0 apply fmt 2018-09-03 17:52:05 +02:00
Dawid Dziurla
6a99d36ae1 change key from 'H' to '?' 2018-09-03 17:52:05 +02:00
Dawid Dziurla
2416f585ce initial help panel 2018-09-03 17:52:05 +02:00
Dawid Dziurla
741e28d01a move bindings to getKeybindings() 2018-09-03 17:52:05 +02:00
Jesse Duffield
19a8029795 Merge pull request #250 from dawidd6/fix/tests
Fix testing with localized rmdir + don't test scripts directory
2018-09-03 22:04:16 +10:00
Jesse Duffield
796f17eef4 Merge branch 'master' into fix/tests 2018-09-03 21:20:17 +10:00
Jesse Duffield
8fbb3aea4f Merge pull request #242 from d-dorazio/rename-commits-in-user-editor
add keybinding to open user editor when renaming last commit
2018-09-03 20:11:13 +10:00
Jesse Duffield
6fc4cb1b96 Merge branch 'master' into rename-commits-in-user-editor 2018-09-03 19:53:16 +10:00
Jesse Duffield
3c1935fee4 Merge pull request #249 from jesseduffield/hotfix/238-xdg-open
238: opening with xdg-open
2018-09-03 19:48:42 +10:00
Jesse Duffield
dfb87d34dc Merge branch 'master' into hotfix/238-xdg-open 2018-09-03 19:42:06 +10:00
Jesse Duffield
4ab1a1f72b Merge branch 'master' into hotfix/238-xdg-open 2018-09-03 19:33:37 +10:00
Jesse Duffield
a9cd277070 add test for ResolvePlaceholderString 2018-09-03 19:31:27 +10:00
Dawid Dziurla
5c1463313d respect localized output of rmdir 2018-09-01 17:22:49 +02:00
Dawid Dziurla
0355fc3008 don't run tests on scripts/ directory 2018-09-01 17:22:23 +02:00
Daniele D'Orazio
39f065207e add simple test for PrepareCommitAmendSubProcess 2018-09-01 12:29:43 +02:00
Daniele D'Orazio
9e6a4a529a add keybinding to open user editor when renaming last commit 2018-09-01 12:14:42 +02:00
Jesse Duffield
87de803a6c update default config header 2018-09-01 14:38:30 +10:00
Jesse Duffield
3cafa2bb12 update config to reflect platform specific defaults 2018-09-01 14:35:46 +10:00
Jesse Duffield
d31520261f introduce platform specific defaults 2018-09-01 14:33:01 +10:00
Jesse Duffield
ad880e2d56 wrap windows start command in shell 2018-09-01 13:28:10 +10:00
Jesse Duffield
865809e625 better error handling for commands 2018-09-01 13:27:58 +10:00
Jesse Duffield
04d5a473d7 use start instead of cygstart to open files on windows 2018-09-01 12:53:51 +10:00
Jesse Duffield
f127ae62bb update config 2018-09-01 12:18:16 +10:00
Jesse Duffield
3f14b764d5 update tests 2018-09-01 12:13:41 +10:00
Jesse Duffield
ae0d88f855 WIP using runDirectCommand with xdg-open 2018-09-01 11:38:32 +10:00
Jesse Duffield
b65fa852f1 Merge pull request #248 from jesseduffield/hotfix/better-backwards-compatibility-error
Better error for nonbackwards compatible changes
2018-09-01 11:26:58 +10:00
Jesse Duffield
42500817e0 better error for nonbackwards compatible changes 2018-09-01 10:36:55 +10:00
Jesse Duffield
d8aba3aeee Merge pull request #241 from antham/master
Fix linting issues
2018-08-29 23:05:14 +10:00
antham
1b8836e92d fix fmt issue 2018-08-29 13:46:51 +02:00
Anthony HAMON
54326907c3 fix linting issues 2018-08-29 13:46:51 +02:00
Anthony HAMON
cda7b374e2 fix linting issues 2018-08-29 13:46:51 +02:00
Anthony HAMON
e889a40caf fix golint issue 2018-08-29 13:46:51 +02:00
Jesse Duffield
3f26ddc06f Merge pull request #225 from antham/master
Add tests to pkg/commands/git - Part 1
2018-08-29 20:25:39 +10:00
Anthony HAMON
dac7c90483 add cache based on Gopkg.lock checksum 2018-08-29 12:03:32 +02:00
Anthony HAMON
66e5dacf5e fix git tests 2018-08-29 12:03:32 +02:00
Anthony HAMON
e3ed899b20 refactor MergeStatusFiles 2018-08-29 12:03:32 +02:00
Anthony HAMON
d6b4d4b063 add tests for MergesStatusFiles 2018-08-29 12:03:32 +02:00
Anthony HAMON
45fa257128 add test for StashSave and refactor StashSave method 2018-08-29 12:03:32 +02:00
Anthony HAMON
99840d8fc4 add test for StashDo and refactor StashDo method 2018-08-29 12:03:32 +02:00
Anthony HAMON
85012dbc8f add tests for GetStatusFiles 2018-08-29 12:03:32 +02:00
Anthony HAMON
13f9073552 add test for GetStashEntryDiff 2018-08-29 12:03:32 +02:00
Anthony HAMON
49b507d2ff replace make 2018-08-29 12:03:32 +02:00
Anthony HAMON
8247fd69c9 add test for GetStashEntries 2018-08-29 12:03:32 +02:00
Anthony HAMON
983d0bd586 replace make 2018-08-29 12:03:32 +02:00
Anthony HAMON
ca9ce22693 use assert in tests, rename testing method 2018-08-29 12:03:32 +02:00
Jesse Duffield
cff1dee6dc more lenient version comparison 2018-08-29 09:37:47 +10:00
44 changed files with 2262 additions and 489 deletions

View File

@@ -2,14 +2,24 @@ version: 2
jobs:
build:
docker:
- image: circleci/golang:1.10
- image: circleci/golang:1.11
working_directory: /go/src/github.com/jesseduffield/lazygit
steps:
- checkout
- restore_cache:
keys:
- v1-pkg-cache
- run:
name: Ensure go.mod file is up to date
command: |
export GO111MODULE=on
rm go.sum
mv go.mod /tmp/
go mod init
export GO111MODULE=auto
if [ $(diff /tmp/go.mod go.mod|wc -l) -gt 0 ]; then
diff /tmp/go.mod go.mod
exit 1;
fi
- run:
name: Run gofmt -s
command: |
@@ -17,6 +27,9 @@ jobs:
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
exit 1;
fi
- restore_cache:
keys:
- pkg-cache-{{ checksum "Gopkg.lock" }}-v2
- run:
name: Run tests
command: |
@@ -31,9 +44,9 @@ jobs:
command: |
bash <(curl -s https://codecov.io/bash)
- save_cache:
key: v1-pkg-cache
key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2
paths:
- "/go/pkg"
- ~/.cache/go-build
release:
docker:

17
.gitignore vendored
View File

@@ -3,13 +3,14 @@
# Logs
*.log
# Extras
extra/lgit.rb
# Hidden
.*
# TODO
TODO.*
# Notes
notes/go.notes
TODO.notes
TODO.md
*.notes
# Tests
test/repos/repo
@@ -17,3 +18,9 @@ coverage.txt
# Binaries
lazygit
# Exceptions
!.gitignore
!.goreleaser.yml
!.circleci/
!.github/

View File

@@ -15,9 +15,10 @@ welcome your pull requests:
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've added code that need documentation, update the documentation.
4. Be sure to test your modifications.
5. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
6. Issue that pull request!
4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible.
5. Be sure to test your modifications.
6. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
7. Issue that pull request!
## Code of conduct
Please note by participating in this project, you agree to abide by the [code of conduct].

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
# run with:
# docker build -t lazygit .
# docker run -it lazygit:latest
FROM golang:alpine
RUN apk add -U git xdg-utils
ADD . /go/src/github.com/jesseduffield/lazygit
RUN go install github.com/jesseduffield/lazygit
WORKDIR /go/src/github.com/jesseduffield/lazygit

5
Gopkg.lock generated
View File

@@ -189,11 +189,11 @@
[[projects]]
branch = "master"
digest = "1:acbcdae312c37a8019e0f573a9be26499058d5e1244243655373d2fd97714658"
digest = "1:71e6c15797951d3fabaa944d70253e36a6cee96bf54ca0bc43ca3de3b4814bbb"
name = "github.com/jesseduffield/gocui"
packages = ["."]
pruneopts = "NUT"
revision = "5d9e836837237cb3aadca51ecb37c7cf3345bfa4"
revision = "2cb6e95bbbf850bb32cc1799e07d08ff0f144746"
[[projects]]
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
@@ -611,7 +611,6 @@
analyzer-version = 1
input-imports = [
"github.com/cloudfoundry/jibber_jabber",
"github.com/davecgh/go-spew/spew",
"github.com/fatih/color",
"github.com/golang-collections/collections/stack",
"github.com/heroku/rollrus",

View File

@@ -131,3 +131,7 @@ feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[
If you want to see what I (Jesse) am up to in terms of development, follow me on
[twitter](https://twitter.com/DuffieldJesse) or watch me program on
[twitch](https://www.twitch.tv/jesseduffield).
## Alternatives
If you find that lazygit doesn't quite satisfy your requirements, these may be a better fit:
- [tig](https://github.com/jonas/tig)

View File

@@ -14,14 +14,45 @@
- white
optionsTextColor:
- blue
git:
# stuff relating to git
os:
# stuff relating to the OS
commitLength:
show: true
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
confirmOnQuit: false
```
## Platform Defaults:
### Windows:
```
os:
openCommand: 'cmd /c "start "" {{filename}}"'
```
### Linux:
```
os:
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
```
### OSX:
```
os:
openCommand: 'open {{filename}}'
```
### Recommended Config Values:
for users of VSCode
```
os:
openCommand: 'code -r {{filename}}'
```
## Color Attributes:

View File

@@ -54,6 +54,7 @@
<pre>
<kbd>s</kbd>: squash down (only available for topmost commit)
<kbd>r</kbd>: rename commit
<kbd>shift</kbd>+<kbd>R</kbd>: rename commit using git editor
<kbd>g</kbd>: reset to this commit
</pre>

62
go.mod Normal file
View File

@@ -0,0 +1,62 @@
module github.com/jesseduffield/lazygit
require (
github.com/aws/aws-sdk-go v1.15.21
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/davecgh/go-spew v1.1.0
github.com/emirpasic/gods v1.9.0
github.com/fatih/color v1.7.0
github.com/fsnotify/fsnotify v1.4.7
github.com/go-ini/ini v1.38.2
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc
github.com/hashicorp/go-version v1.0.0
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55
github.com/magiconair/properties v1.8.0
github.com/mattn/go-colorable v0.0.9
github.com/mattn/go-isatty v0.0.3
github.com/mattn/go-runewidth v0.0.2
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb
github.com/pelletier/go-buffruneio v0.2.0
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0
github.com/sergi/go-diff v1.0.0
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
github.com/sirupsen/logrus v1.0.6
github.com/spf13/afero v1.1.1
github.com/spf13/cast v1.2.0
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834
github.com/spf13/pflag v1.0.2
github.com/spf13/viper v1.1.0
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/src-d/gcfg v1.3.0
github.com/stretchr/testify v1.2.2
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea
github.com/tcnksm/go-gitconfig v0.1.2
github.com/ulikunitz/xz v0.5.4
github.com/xanzy/ssh-agent v0.2.0
golang.org/x/crypto v0.0.0-20180808211826-de0752318171
golang.org/x/net v0.0.0-20180811021610-c39426892332
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0
golang.org/x/text v0.3.0
gopkg.in/src-d/go-billy.v4 v4.2.0
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714
gopkg.in/warnings.v0 v0.1.2
gopkg.in/yaml.v2 v2.2.1
)

163
go.sum Normal file
View File

@@ -0,0 +1,163 @@
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/aws/aws-sdk-go v1.15.21 h1:STLvc6RrpycslC1NRtTvt/YSgDkIGCTrB9K9vE5R2oQ=
github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ini/ini v1.38.2 h1:6Hl/z3p3iFkA0dlDfzYxuFuUGD+kaweypF6btsR2/Q4=
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo=
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 h1:MFPzqpPED05pFyGjNPJEC2sXM6EHTzFyvX+0s0JoZ48=
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU=
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc h1:wAa9fGALVHfjYxZuXRnmuJG2CnwRpJYOTvY6YdErAh0=
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331 h1:qio0y/sQdhbHRA3cmgczo04MaSV2zw+n46G1owvgWIk=
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331/go.mod h1:BT+PgT529opmb6mcUY+Fg0IwVRRmwqFyavEMU17GnBg=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns=
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0=
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8 h1:YMYnpIu5HUJfx/yfIwnZhFrgWTg51FQWtvZi+PMzQm8=
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 h1:S38dC4mEwxdw/U41+97VWdbun8mTcTjwg5Ujfg8QPME=
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
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 v0.0.0-20180801233206-58046073cbff h1:jM4Eo4qMmmcqePS3u6X2lcEELtVuXWkWJIS/pRI3oSk=
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI=
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co=
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I=
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 h1:kJI9pPzfsULT/72wy7mxkRQZPtKWgFdCA2RTGZ4v8/E=
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4=
github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
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/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg=
github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea h1:jysxIKov/4GJ33wI2aRvuIK7yBwB28E5almlgDLPRpM=
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea/go.mod h1:Ffmqrj3nXIMIjeA4uW3Qjj0Ud9eDoTG0fu4JxyAr/tE=
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
golang.org/x/crypto v0.0.0-20180808211826-de0752318171 h1:vYogbvSFj2YXcjQxFHu/rASSOt9sLytpCaSkiwQ135I=
golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 h1:8H8QZJ30plJyIVj60H3lr8TZGIq2Fh3Cyrs/ZNg1foU=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/ini.v1 v1.38.2 h1:dGcbywv4RufeGeiMycPT/plKB5FtmLKLnWKwBiLhUA4=
gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/src-d/go-billy.v4 v4.2.0 h1:VGbrP1EsYxtvVPEiHui+4//imr4E5MGEFLx66bQtusg=
gopkg.in/src-d/go-billy.v4 v4.2.0/go.mod h1:ZHSF0JP+7oD97194otDUCD7Ofbk63+xFcfWP5bT6h+Q=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714 h1:+wM2BGgQ1znCKBexOB4OrGVSDw8mtKNUSq3wqxZhi/k=
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -43,11 +43,11 @@ func main() {
panic(err)
}
app, err := app.NewApp(appConfig)
app, err := app.Setup(appConfig)
if err != nil {
app.Log.Error(err.Error())
panic(err)
}
app.GitCommand.SetupGit()
app.Gui.RunWithSubprocesses()
}

View File

@@ -65,15 +65,15 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
})
}
// NewApp retruns a new applications
func NewApp(config config.AppConfigurer) (*App, error) {
// Setup bootstrap a new application
func Setup(config config.AppConfigurer) (*App, error) {
app := &App{
closers: []io.Closer{},
Config: config,
}
var err error
app.Log = newLogger(config)
app.OSCommand = commands.NewOSCommand(app.Log)
app.OSCommand = commands.NewOSCommand(app.Log, config)
app.Tr = i18n.NewLocalizer(app.Log)

View File

@@ -15,6 +15,48 @@ import (
gogit "gopkg.in/src-d/go-git.v4"
)
func verifyInGitRepo(runCmd func(string) error) error {
return runCmd("git status")
}
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
for {
f, err := stat(".git")
if err == nil && f.IsDir() {
return nil
}
if !os.IsNotExist(err) {
return err
}
if err = chdir(".."); err != nil {
return err
}
}
}
func setupRepositoryAndWorktree(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (repository *gogit.Repository, worktree *gogit.Worktree, err error) {
repository, err = openGitRepository(".")
if err != nil {
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
return nil, nil, errors.New(sLocalize("GitconfigParseErr"))
}
return
}
worktree, err = repository.Worktree()
if err != nil {
return
}
return
}
// GitCommand is our main git interface
type GitCommand struct {
Log *logrus.Entry
@@ -26,28 +68,42 @@ type GitCommand struct {
// NewGitCommand it runs git commands
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) {
gitCommand := &GitCommand{
var worktree *gogit.Worktree
var repo *gogit.Repository
fs := []func() error{
func() error {
return verifyInGitRepo(osCommand.RunCommand)
},
func() error {
return navigateToRepoRootDirectory(os.Stat, os.Chdir)
},
func() error {
var err error
repo, worktree, err = setupRepositoryAndWorktree(gogit.PlainOpen, tr.SLocalize)
return err
},
}
for _, f := range fs {
if err := f(); err != nil {
return nil, err
}
}
return &GitCommand{
Log: log,
OSCommand: osCommand,
Tr: tr,
}
return gitCommand, nil
}
// SetupGit sets git repo up
func (c *GitCommand) SetupGit() {
c.verifyInGitRepo()
c.navigateToRepoRootDirectory()
if err := c.setupWorktree(); err != nil {
c.Log.Error(err)
panic(err)
}
Worktree: worktree,
Repo: repo,
}, nil
}
// GetStashEntries stash entryies
func (c *GitCommand) GetStashEntries() []StashEntry {
stashEntries := make([]StashEntry, 0)
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
stashEntries := []StashEntry{}
for i, line := range utils.SplitLines(rawString) {
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
}
@@ -67,33 +123,26 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{" + fmt.Sprint(index) + "}")
}
func includes(array []string, str string) bool {
for _, arrayStr := range array {
if arrayStr == str {
return true
}
}
return false
}
// GetStatusFiles git status files
func (c *GitCommand) GetStatusFiles() []File {
statusOutput, _ := c.GitStatus()
statusStrings := utils.SplitLines(statusOutput)
files := make([]File, 0)
files := []File{}
for _, statusString := range statusStrings {
change := statusString[0:2]
stagedChange := change[0:1]
unstagedChange := statusString[1:2]
filename := c.OSCommand.Unquote(statusString[3:])
tracked := !includes([]string{"??", "A ", "AM"}, change)
_, untracked := map[string]bool{"??": true, "A ": true, "AM": true}[change]
_, hasNoStagedChanges := map[string]bool{" ": true, "U": true, "?": true}[stagedChange]
file := File{
Name: filename,
DisplayString: statusString,
HasStagedChanges: !includes([]string{" ", "U", "?"}, stagedChange),
HasStagedChanges: !hasNoStagedChanges,
HasUnstagedChanges: unstagedChange != " ",
Tracked: tracked,
Tracked: !untracked,
Deleted: unstagedChange == "D" || stagedChange == "D",
HasMergeConflicts: change == "UU",
Type: c.OSCommand.FileType(filename),
@@ -106,13 +155,13 @@ func (c *GitCommand) GetStatusFiles() []File {
// StashDo modify stash
func (c *GitCommand) StashDo(index int, method string) error {
return c.OSCommand.RunCommand("git stash " + method + " stash@{" + fmt.Sprint(index) + "}")
return c.OSCommand.RunCommand(fmt.Sprintf("git stash %s stash@{%d}", method, index))
}
// StashSave save stash
// TODO: before calling this, check if there is anything to save
func (c *GitCommand) StashSave(message string) error {
return c.OSCommand.RunCommand("git stash save " + c.OSCommand.Quote(message))
return c.OSCommand.RunCommand(fmt.Sprintf("git stash save %s", c.OSCommand.Quote(message)))
}
// MergeStatusFiles merge status files
@@ -121,35 +170,28 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File {
return newFiles
}
appendedIndexes := make([]int, 0)
headResults := []File{}
tailResults := []File{}
// retain position of files we already could see
result := make([]File, 0)
for _, oldFile := range oldFiles {
for newIndex, newFile := range newFiles {
for _, newFile := range newFiles {
var isHeadResult bool
for _, oldFile := range oldFiles {
if oldFile.Name == newFile.Name {
result = append(result, newFile)
appendedIndexes = append(appendedIndexes, newIndex)
isHeadResult = true
break
}
}
}
// append any new files to the end
for index, newFile := range newFiles {
if !includesInt(appendedIndexes, index) {
result = append(result, newFile)
if isHeadResult {
headResults = append(headResults, newFile)
continue
}
tailResults = append(tailResults, newFile)
}
return result
}
func (c *GitCommand) verifyInGitRepo() {
if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil {
fmt.Println(output)
os.Exit(1)
}
return append(headResults, tailResults...)
}
// GetBranchName branch name
@@ -157,34 +199,6 @@ func (c *GitCommand) GetBranchName() (string, error) {
return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
}
func (c *GitCommand) navigateToRepoRootDirectory() {
_, err := os.Stat(".git")
for os.IsNotExist(err) {
c.Log.Debug("going up a directory to find the root")
os.Chdir("..")
_, err = os.Stat(".git")
}
}
func (c *GitCommand) setupWorktree() error {
r, err := gogit.PlainOpen(".")
if err != nil {
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
errorMessage := c.Tr.SLocalize("GitconfigParseErr")
return errors.New(errorMessage)
}
return err
}
c.Repo = r
w, err := r.Worktree()
if err != nil {
return err
}
c.Worktree = w
return nil
}
// ResetHard does the equivalent of `git reset --hard HEAD`
func (c *GitCommand) ResetHard() error {
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset})
@@ -209,14 +223,14 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) {
func (c *GitCommand) GetCommitsToPush() []string {
pushables, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit")
if err != nil {
return make([]string, 0)
return []string{}
}
return utils.SplitLines(pushables)
}
// RenameCommit renames the topmost commit with the given name
func (c *GitCommand) RenameCommit(name string) error {
return c.OSCommand.RunCommand("git commit --allow-empty --amend -m " + c.OSCommand.Quote(name))
return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name)))
}
// Fetch fetch git repo
@@ -226,23 +240,23 @@ func (c *GitCommand) Fetch() error {
// ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string) error {
return c.OSCommand.RunCommand("git reset " + sha)
return c.OSCommand.RunCommand(fmt.Sprintf("git reset %s", sha))
}
// NewBranch create new branch
func (c *GitCommand) NewBranch(name string) error {
return c.OSCommand.RunCommand("git checkout -b " + name)
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name))
}
// DeleteBranch delete branch
func (c *GitCommand) DeleteBranch(branch string, force bool) error {
var command string
command := "git branch -d"
if force {
command = "git branch -D "
} else {
command = "git branch -d "
command = "git branch -D"
}
return c.OSCommand.RunCommand(command + branch)
return c.OSCommand.RunCommand(fmt.Sprintf("%s %s", command, branch))
}
// ListStash list stash
@@ -252,7 +266,7 @@ func (c *GitCommand) ListStash() (string, error) {
// Merge merge
func (c *GitCommand) Merge(branchName string) error {
return c.OSCommand.RunCommand("git merge --no-edit " + branchName)
return c.OSCommand.RunCommand(fmt.Sprintf("git merge --no-edit %s", branchName))
}
// AbortMerge abort merge
@@ -422,6 +436,11 @@ func (c *GitCommand) PrepareCommitSubProcess() *exec.Cmd {
return c.OSCommand.PrepareSubProcess("git", "commit")
}
// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
return c.OSCommand.PrepareSubProcess("git", "commit", "--amend", "--allow-empty")
}
// GetBranchGraph gets the color-formatted graph of the log for the given branch
// Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading
@@ -429,15 +448,6 @@ func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName)
}
// Map (from https://gobyexample.com/collection-functions)
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
func includesString(list []string, a string) bool {
for _, b := range list {
if b == a {
@@ -447,22 +457,11 @@ func includesString(list []string, a string) bool {
return false
}
// not sure how to genericise this because []interface{} doesn't accept e.g.
// []int arguments
func includesInt(list []int, a int) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// GetCommits obtains the commits of the current branch
func (c *GitCommand) GetCommits() []Commit {
pushables := c.GetCommitsToPush()
log := c.GetLog()
commits := make([]Commit, 0)
commits := []Commit{}
// now we can split it up and turn it into commits
lines := utils.SplitLines(log)
for _, line := range lines {

View File

@@ -1,14 +1,53 @@
package commands
import (
"fmt"
"io/ioutil"
"strings"
"os"
"os/exec"
"testing"
"time"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/test"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
gogit "gopkg.in/src-d/go-git.v4"
)
type fileInfoMock struct {
name string
size int64
fileMode os.FileMode
fileModTime time.Time
isDir bool
sys interface{}
}
func (f fileInfoMock) Name() string {
return f.name
}
func (f fileInfoMock) Size() int64 {
return f.size
}
func (f fileInfoMock) Mode() os.FileMode {
return f.fileMode
}
func (f fileInfoMock) ModTime() time.Time {
return f.fileModTime
}
func (f fileInfoMock) IsDir() bool {
return f.isDir
}
func (f fileInfoMock) Sys() interface{} {
return f.sys
}
func newDummyLog() *logrus.Entry {
log := logrus.New()
log.Out = ioutil.Discard
@@ -19,14 +58,682 @@ func newDummyGitCommand() *GitCommand {
return &GitCommand{
Log: newDummyLog(),
OSCommand: newDummyOSCommand(),
Tr: i18n.NewLocalizer(newDummyLog()),
}
}
func TestDiff(t *testing.T) {
gitCommand := newDummyGitCommand()
if err := test.GenerateRepo("lots_of_diffs.sh"); err != nil {
t.Error(err.Error())
func TestVerifyInGitRepo(t *testing.T) {
type scenario struct {
testName string
runCmd func(string) error
test func(error)
}
scenarios := []scenario{
{
"Valid git repository",
func(string) error {
return nil
},
func(err error) {
assert.NoError(t, err)
},
},
{
"Not a valid git repository",
func(string) error {
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
},
func(err error) {
assert.Error(t, err)
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(verifyInGitRepo(s.runCmd))
})
}
}
func TestNavigateToRepoRootDirectory(t *testing.T) {
type scenario struct {
testName string
stat func(string) (os.FileInfo, error)
chdir func(string) error
test func(error)
}
scenarios := []scenario{
{
"Navigate to git repository",
func(string) (os.FileInfo, error) {
return fileInfoMock{isDir: true}, nil
},
func(string) error {
return nil
},
func(err error) {
assert.NoError(t, err)
},
},
{
"An error occurred when getting path informations",
func(string) (os.FileInfo, error) {
return nil, fmt.Errorf("An error occurred")
},
func(string) error {
return nil
},
func(err error) {
assert.Error(t, err)
assert.EqualError(t, err, "An error occurred")
},
},
{
"An error occurred when trying to move one path backward",
func(string) (os.FileInfo, error) {
return nil, os.ErrNotExist
},
func(string) error {
return fmt.Errorf("An error occurred")
},
func(err error) {
assert.Error(t, err)
assert.EqualError(t, err, "An error occurred")
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(navigateToRepoRootDirectory(s.stat, s.chdir))
})
}
}
func TestSetupRepositoryAndWorktree(t *testing.T) {
type scenario struct {
testName string
openGitRepository func(string) (*gogit.Repository, error)
sLocalize func(string) string
test func(*gogit.Repository, *gogit.Worktree, error)
}
scenarios := []scenario{
{
"A gitconfig parsing error occurred",
func(string) (*gogit.Repository, error) {
return nil, fmt.Errorf(`unquoted '\' must be followed by new line`)
},
func(string) string {
return "error translated"
},
func(r *gogit.Repository, w *gogit.Worktree, err error) {
assert.Error(t, err)
assert.EqualError(t, err, "error translated")
},
},
{
"A gogit error occurred",
func(string) (*gogit.Repository, error) {
return nil, fmt.Errorf("Error from inside gogit")
},
func(string) string { return "" },
func(r *gogit.Repository, w *gogit.Worktree, err error) {
assert.Error(t, err)
assert.EqualError(t, err, "Error from inside gogit")
},
},
{
"An error occurred cause git repository is a bare repository",
func(string) (*gogit.Repository, error) {
return &gogit.Repository{}, nil
},
func(string) string { return "" },
func(r *gogit.Repository, w *gogit.Worktree, err error) {
assert.Error(t, err)
assert.Equal(t, gogit.ErrIsBareRepository, err)
},
},
{
"Setup done properly",
func(string) (*gogit.Repository, error) {
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
r, err := gogit.PlainInit("/tmp/lazygit-test", false)
assert.NoError(t, err)
return r, nil
},
func(string) string { return "" },
func(r *gogit.Repository, w *gogit.Worktree, err error) {
assert.NoError(t, err)
assert.NotNil(t, w)
assert.NotNil(t, r)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize))
})
}
}
func TestNewGitCommand(t *testing.T) {
actual, err := os.Getwd()
assert.NoError(t, err)
defer func() {
assert.NoError(t, os.Chdir(actual))
}()
type scenario struct {
testName string
setup func()
test func(*GitCommand, error)
}
scenarios := []scenario{
{
"An error occurred, folder doesn't contains a git repository",
func() {
assert.NoError(t, os.Chdir("/tmp"))
},
func(gitCmd *GitCommand, err error) {
assert.Error(t, err)
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
},
},
{
"New GitCommand object created",
func() {
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
_, err := gogit.PlainInit("/tmp/lazygit-test", false)
assert.NoError(t, err)
assert.NoError(t, os.Chdir("/tmp/lazygit-test"))
},
func(gitCmd *GitCommand, err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.setup()
s.test(NewGitCommand(newDummyLog(), newDummyOSCommand(), i18n.NewLocalizer(newDummyLog())))
})
}
}
func TestGitCommandGetStashEntries(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func([]StashEntry)
}
scenarios := []scenario{
{
"No stash entries found",
func(string, ...string) *exec.Cmd {
return exec.Command("echo")
},
func(entries []StashEntry) {
assert.Len(t, entries, 0)
},
},
{
"Several stash entries found",
func(string, ...string) *exec.Cmd {
return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template")
},
func(entries []StashEntry) {
expected := []StashEntry{
{
0,
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
},
{
1,
"WIP on master: bb86a3f update github template",
"WIP on master: bb86a3f update github template",
},
}
assert.Len(t, entries, 2)
assert.EqualValues(t, expected, entries)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
s.test(gitCmd.GetStashEntries())
})
}
}
func TestGitCommandGetStashEntryDiff(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"stash", "show", "-p", "--color", "stash@{1}"}, args)
return exec.Command("echo")
}
_, err := gitCmd.GetStashEntryDiff(1)
assert.NoError(t, err)
}
func TestGitCommandGetStatusFiles(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func([]File)
}
scenarios := []scenario{
{
"No files found",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo")
},
func(files []File) {
assert.Len(t, files, 0)
},
},
{
"Several files found",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command(
"echo",
"MM file1.txt\nA file3.txt\nAM file2.txt\n?? file4.txt",
)
},
func(files []File) {
assert.Len(t, files, 4)
expected := []File{
{
Name: "file1.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: true,
Deleted: false,
HasMergeConflicts: false,
DisplayString: "MM file1.txt",
Type: "other",
},
{
Name: "file3.txt",
HasStagedChanges: true,
HasUnstagedChanges: false,
Tracked: false,
Deleted: false,
HasMergeConflicts: false,
DisplayString: "A file3.txt",
Type: "other",
},
{
Name: "file2.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: false,
Deleted: false,
HasMergeConflicts: false,
DisplayString: "AM file2.txt",
Type: "other",
},
{
Name: "file4.txt",
HasStagedChanges: false,
HasUnstagedChanges: true,
Tracked: false,
Deleted: false,
HasMergeConflicts: false,
DisplayString: "?? file4.txt",
Type: "other",
},
}
assert.EqualValues(t, expected, files)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
s.test(gitCmd.GetStatusFiles())
})
}
}
func TestGitCommandStashDo(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"stash", "drop", "stash@{1}"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.StashDo(1, "drop"))
}
func TestGitCommandStashSave(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"stash", "save", "A stash message"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.StashSave("A stash message"))
}
func TestGitCommandCommitAmend(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "--allow-empty"}, args)
return exec.Command("echo")
}
_, err := gitCmd.PrepareCommitAmendSubProcess().CombinedOutput()
assert.NoError(t, err)
}
func TestGitCommandMergeStatusFiles(t *testing.T) {
type scenario struct {
testName string
oldFiles []File
newFiles []File
test func([]File)
}
scenarios := []scenario{
{
"Old file and new file are the same",
[]File{},
[]File{
{
Name: "new_file.txt",
},
},
func(files []File) {
expected := []File{
{
Name: "new_file.txt",
},
}
assert.Len(t, files, 1)
assert.EqualValues(t, expected, files)
},
},
{
"Several files to merge, with some identical",
[]File{
{
Name: "new_file1.txt",
},
{
Name: "new_file2.txt",
},
{
Name: "new_file3.txt",
},
},
[]File{
{
Name: "new_file4.txt",
},
{
Name: "new_file5.txt",
},
{
Name: "new_file1.txt",
},
},
func(files []File) {
expected := []File{
{
Name: "new_file1.txt",
},
{
Name: "new_file4.txt",
},
{
Name: "new_file5.txt",
},
}
assert.Len(t, files, 3)
assert.EqualValues(t, expected, files)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
s.test(gitCmd.MergeStatusFiles(s.oldFiles, s.newFiles))
})
}
}
func TestGitCommandUpstreamDifferentCount(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func(string, string)
}
scenarios := []scenario{
{
"Can't retrieve pushable count",
func(string, ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "?", pushableCount)
assert.EqualValues(t, "?", pullableCount)
},
},
{
"Can't retrieve pullable count",
func(cmd string, args ...string) *exec.Cmd {
if args[1] == "head..@{u}" {
return exec.Command("exit", "1")
}
return exec.Command("echo")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "?", pushableCount)
assert.EqualValues(t, "?", pullableCount)
},
},
{
"Retrieve pullable and pushable count",
func(cmd string, args ...string) *exec.Cmd {
if args[1] == "head..@{u}" {
return exec.Command("echo", "10")
}
return exec.Command("echo", "11")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "11", pushableCount)
assert.EqualValues(t, "10", pullableCount)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
s.test(gitCmd.UpstreamDifferenceCount())
})
}
}
func TestGitCommandGetCommitsToPush(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func([]string)
}
scenarios := []scenario{
{
"Can't retrieve pushable commits",
func(string, ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(pushables []string) {
assert.EqualValues(t, []string{}, pushables)
},
},
{
"Retrieve pushable commits",
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo", "8a2bb0e\n78976bc")
},
func(pushables []string) {
assert.Len(t, pushables, 2)
assert.EqualValues(t, []string{"8a2bb0e", "78976bc"}, pushables)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
s.test(gitCmd.GetCommitsToPush())
})
}
}
func TestGitCommandRenameCommit(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--allow-empty", "--amend", "-m", "test"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.RenameCommit("test"))
}
func TestGitCommandResetToCommit(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"reset", "78976bc"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.ResetToCommit("78976bc"))
}
func TestGitCommandNewBranch(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"checkout", "-b", "test"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.NewBranch("test"))
}
func TestGitCommandDeleteBranch(t *testing.T) {
type scenario struct {
testName string
branch string
force bool
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"Delete a branch",
"test",
false,
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"branch", "-d", "test"}, args)
return exec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
},
},
{
"Force delete a branch",
"test",
true,
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"branch", "-D", "test"}, args)
return exec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = s.command
s.test(gitCmd.DeleteBranch(s.branch, s.force))
})
}
}
func TestGitCommandMerge(t *testing.T) {
gitCmd := newDummyGitCommand()
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"merge", "--no-edit", "test"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.Merge("test"))
}
func TestGitCommandDiff(t *testing.T) {
gitCommand := newDummyGitCommand()
assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh"))
files := []File{
{
Name: "deleted_staged",
@@ -110,10 +817,10 @@ func TestDiff(t *testing.T) {
DisplayString: "?? master",
},
}
for _, file := range files {
content := gitCommand.Diff(file)
if strings.Contains(content, "error") {
t.Error("Error: diff test failed. File: " + file.Name + ", " + content)
}
t.Run(file.Name, func(t *testing.T) {
assert.NotContains(t, gitCommand.Diff(file), "error")
})
}
}

View File

@@ -6,7 +6,8 @@ import (
"os/exec"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
@@ -16,26 +17,30 @@ import (
// Platform stores the os state
type Platform struct {
os string
shell string
shellArg string
escapedQuote string
os string
shell string
shellArg string
escapedQuote string
openCommand string
fallbackEscapedQuote string
}
// OSCommand holds all the os commands
type OSCommand struct {
Log *logrus.Entry
Platform *Platform
Config config.AppConfigurer
command func(string, ...string) *exec.Cmd
getGlobalGitConfig func(string) (string, error)
getenv func(string) string
}
// NewOSCommand os command runner
func NewOSCommand(log *logrus.Entry) *OSCommand {
func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
return &OSCommand{
Log: log,
Platform: getPlatform(),
Config: config,
command: exec.Command,
getGlobalGitConfig: gitconfig.Global,
getenv: os.Getenv,
@@ -47,7 +52,6 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
c.Log.WithField("command", command).Info("RunCommand")
splitCmd := str.ToArgv(command)
c.Log.Info(splitCmd)
return sanitisedCommandOutput(
c.command(splitCmd[0], splitCmd[1:]...).CombinedOutput(),
)
@@ -74,12 +78,9 @@ func (c *OSCommand) FileType(path string) string {
// RunDirectCommand wrapper around direct commands
func (c *OSCommand) RunDirectCommand(command string) (string, error) {
c.Log.WithField("command", command).Info("RunDirectCommand")
args := str.ToArgv(c.Platform.shellArg + " " + command)
c.Log.Info(spew.Sdump(args))
return sanitisedCommandOutput(
exec.
Command(c.Platform.shell, args...).
c.command(c.Platform.shell, c.Platform.shellArg, command).
CombinedOutput(),
)
}
@@ -89,51 +90,24 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
if err != nil {
// errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output
if outputString == "" {
return "", err
}
return outputString, errors.New(outputString)
}
return outputString, nil
}
// getOpenCommand get open command
func (c *OSCommand) getOpenCommand() (string, string, error) {
//NextStep open equivalents: xdg-open (linux), cygstart (cygwin), open (OSX)
trailMap := map[string]string{
"xdg-open": " &>/dev/null &",
"cygstart": "",
"open": "",
}
for name, trail := range trailMap {
if err := c.RunCommand("which " + name); err == nil {
return name, trail, nil
}
}
return "", "", errors.New("Unsure what command to use to open this file")
}
// VsCodeOpenFile opens the file in code, with the -r flag to open in the
// current window
// each of these open files needs to have the same function signature because
// they're being passed as arguments into another function,
// but only editFile actually returns a *exec.Cmd
func (c *OSCommand) VsCodeOpenFile(filename string) (*exec.Cmd, error) {
return nil, c.RunCommand("code -r " + filename)
}
// SublimeOpenFile opens the filein sublime
// may be deprecated in the future
func (c *OSCommand) SublimeOpenFile(filename string) (*exec.Cmd, error) {
return nil, c.RunCommand("subl " + filename)
}
// OpenFile opens a file with the given
func (c *OSCommand) OpenFile(filename string) error {
cmdName, cmdTrail, err := c.getOpenCommand()
if err != nil {
return err
commandTemplate := c.Config.GetUserConfig().GetString("os.openCommand")
templateValues := map[string]string{
"filename": c.Quote(filename),
}
return c.RunCommand(cmdName + " " + c.Quote(filename) + cmdTrail) // TODO: test on linux
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.RunCommand(command)
return err
}
// EditFile opens a file in a subprocess using whatever editor is available,
@@ -167,7 +141,11 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *ex
// Quote wraps a message in platform-specific quotation marks
func (c *OSCommand) Quote(message string) string {
message = strings.Replace(message, "`", "\\`", -1)
return c.Platform.escapedQuote + message + c.Platform.escapedQuote
escapedQuote := c.Platform.escapedQuote
if strings.Contains(message, c.Platform.escapedQuote) {
escapedQuote = c.Platform.fallbackEscapedQuote
}
return escapedQuote + message + escapedQuote
}
// Unquote removes wrapping quotations marks if they are present

View File

@@ -8,9 +8,11 @@ import (
func getPlatform() *Platform {
return &Platform{
os: runtime.GOOS,
shell: "bash",
shellArg: "-c",
escapedQuote: "\"",
os: runtime.GOOS,
shell: "bash",
shellArg: "-c",
escapedQuote: "'",
openCommand: "open {{filename}}",
fallbackEscapedQuote: "\"",
}
}

View File

@@ -5,11 +5,28 @@ import (
"os/exec"
"testing"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
yaml "gopkg.in/yaml.v2"
)
func newDummyOSCommand() *OSCommand {
return NewOSCommand(newDummyLog())
return NewOSCommand(newDummyLog(), newDummyAppConfig())
}
func newDummyAppConfig() *config.AppConfig {
appConfig := &config.AppConfig{
Name: "lazygit",
Version: "unversioned",
Commit: "",
BuildDate: "",
Debug: false,
BuildSource: "",
UserConfig: viper.New(),
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
return appConfig
}
func TestOSCommandRunCommandWithOutput(t *testing.T) {
@@ -29,7 +46,7 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
{
"rmdir unexisting-folder",
func(output string, err error) {
assert.Regexp(t, ".*No such file or directory.*", err.Error())
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
},
},
}
@@ -49,7 +66,7 @@ func TestOSCommandRunCommand(t *testing.T) {
{
"rmdir unexisting-folder",
func(err error) {
assert.Regexp(t, ".*No such file or directory.*", err.Error())
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
},
},
}
@@ -59,44 +76,6 @@ func TestOSCommandRunCommand(t *testing.T) {
}
}
func TestOSCommandGetOpenCommand(t *testing.T) {
type scenario struct {
command func(string, ...string) *exec.Cmd
test func(string, string, error)
}
scenarios := []scenario{
{
func(name string, arg ...string) *exec.Cmd {
return exec.Command("exit", "1")
},
func(name string, trail string, err error) {
assert.EqualError(t, err, "Unsure what command to use to open this file")
},
},
{
func(name string, arg ...string) *exec.Cmd {
assert.Equal(t, "which", name)
assert.Len(t, arg, 1)
assert.Regexp(t, "xdg-open|cygstart|open", arg[0])
return exec.Command("echo")
},
func(name string, trail string, err error) {
assert.NoError(t, err)
assert.Regexp(t, "xdg-open|cygstart|open", name)
assert.Regexp(t, " \\&\\>/dev/null \\&|", trail)
},
},
}
for _, s := range scenarios {
OSCmd := newDummyOSCommand()
OSCmd.command = s.command
s.test(OSCmd.getOpenCommand())
}
}
func TestOSCommandOpenFile(t *testing.T) {
type scenario struct {
filename string
@@ -111,29 +90,25 @@ func TestOSCommandOpenFile(t *testing.T) {
return exec.Command("exit", "1")
},
func(err error) {
assert.EqualError(t, err, "Unsure what command to use to open this file")
assert.Error(t, err)
},
},
{
"test",
func(name string, arg ...string) *exec.Cmd {
if name == "which" {
return exec.Command("echo")
}
switch len(arg) {
case 1:
assert.Regexp(t, "open|cygstart", name)
assert.EqualValues(t, "test", arg[0])
case 3:
assert.Equal(t, "xdg-open", name)
assert.EqualValues(t, "test", arg[0])
assert.Regexp(t, " \\&\\>/dev/null \\&|", arg[1])
assert.EqualValues(t, "&", arg[2])
default:
assert.Fail(t, "Unexisting command given")
}
assert.Equal(t, "open", name)
assert.Equal(t, []string{"test"}, arg)
return exec.Command("echo")
},
func(err error) {
assert.NoError(t, err)
},
},
{
"filename with spaces",
func(name string, arg ...string) *exec.Cmd {
assert.Equal(t, "open", name)
assert.Equal(t, []string{"filename with spaces"}, arg)
return exec.Command("echo")
},
func(err error) {
@@ -145,6 +120,7 @@ func TestOSCommandOpenFile(t *testing.T) {
for _, s := range scenarios {
OSCmd := newDummyOSCommand()
OSCmd.command = s.command
OSCmd.Config.GetUserConfig().Set("os.openCommand", "open {{filename}}")
s.test(OSCmd.OpenFile(s.filename))
}
@@ -289,6 +265,32 @@ func TestOSCommandQuote(t *testing.T) {
assert.EqualValues(t, expected, actual)
}
// TestOSCommandQuoteSingleQuote tests the quote function with ' quotes explicitly for Linux
func TestOSCommandQuoteSingleQuote(t *testing.T) {
osCommand := newDummyOSCommand()
osCommand.Platform.os = "linux"
actual := osCommand.Quote("hello 'test'")
expected := osCommand.Platform.fallbackEscapedQuote + "hello 'test'" + osCommand.Platform.fallbackEscapedQuote
assert.EqualValues(t, expected, actual)
}
// TestOSCommandQuoteSingleQuote tests the quote function with " quotes explicitly for Linux
func TestOSCommandQuoteDoubleQuote(t *testing.T) {
osCommand := newDummyOSCommand()
osCommand.Platform.os = "linux"
actual := osCommand.Quote(`hello "test"`)
expected := osCommand.Platform.escapedQuote + "hello \"test\"" + osCommand.Platform.escapedQuote
assert.EqualValues(t, expected, actual)
}
func TestOSCommandUnquote(t *testing.T) {
osCommand := newDummyOSCommand()

View File

@@ -2,9 +2,10 @@ package commands
func getPlatform() *Platform {
return &Platform{
os: "windows",
shell: "cmd",
shellArg: "/c",
escapedQuote: "\\\"",
os: "windows",
shell: "cmd",
shellArg: "/c",
escapedQuote: `\"`,
fallbackEscapedQuote: "\\'",
}
}

View File

@@ -40,8 +40,7 @@ type AppConfigurer interface {
// NewAppConfig makes a new app config
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag *bool) (*AppConfig, error) {
defaultConfig := GetDefaultConfig()
userConfig, err := LoadConfig("config", defaultConfig)
userConfig, err := LoadConfig("config", true)
if err != nil {
return nil, err
}
@@ -113,13 +112,16 @@ func newViper(filename string) (*viper.Viper, error) {
}
// LoadConfig gets the user's config
func LoadConfig(filename string, defaults []byte) (*viper.Viper, error) {
func LoadConfig(filename string, withDefaults bool) (*viper.Viper, error) {
v, err := newViper(filename)
if err != nil {
return nil, err
}
if defaults != nil {
if err = LoadDefaults(v, defaults); err != nil {
if withDefaults {
if err = LoadDefaults(v, GetDefaultConfig()); err != nil {
return nil, err
}
if err = LoadDefaults(v, GetPlatformDefaultConfig()); err != nil {
return nil, err
}
}
@@ -131,7 +133,7 @@ func LoadConfig(filename string, defaults []byte) (*viper.Viper, error) {
// LoadDefaults loads in the defaults defined in this file
func LoadDefaults(v *viper.Viper, defaults []byte) error {
return v.ReadConfig(bytes.NewBuffer(defaults))
return v.MergeConfig(bytes.NewBuffer(defaults))
}
func prepareConfigFile(filename string) (string, error) {
@@ -166,7 +168,7 @@ func LoadAndMergeFile(v *viper.Viper, filename string) error {
func (c *AppConfig) WriteToUserConfig(key, value string) error {
// reloading the user config directly (without defaults) so that we're not
// writing any defaults back to the user's config
v, err := LoadConfig("config", nil)
v, err := LoadConfig("config", false)
if err != nil {
return err
}
@@ -190,6 +192,7 @@ func (c *AppConfig) SaveAppState() error {
return ioutil.WriteFile(filepath, marshalledAppState, 0644)
}
// LoadAppState loads recorded AppState from file
func (c *AppConfig) LoadAppState() error {
filepath, err := prepareConfigFile("state.yml")
if err != nil {
@@ -205,6 +208,7 @@ func (c *AppConfig) LoadAppState() error {
return yaml.Unmarshal(appStateBytes, c.AppState)
}
// GetDefaultConfig returns the application default configuration
func GetDefaultConfig() []byte {
return []byte(
`gui:
@@ -218,14 +222,13 @@ func GetDefaultConfig() []byte {
- white
optionsTextColor:
- blue
git:
# stuff relating to git
os:
# stuff relating to the OS
commitLength:
show: true
update:
method: prompt # can be: prompt | background | never
days: 14 # how often a update is checked for
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
confirmOnQuit: false
`)
}

View File

@@ -0,0 +1,10 @@
// +build !windows,!linux
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'open {{filename}}'`)
}

View File

@@ -0,0 +1,8 @@
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'`)
}

View File

@@ -0,0 +1,8 @@
package config
// GetPlatformDefaultConfig gets the defaults for the platform
func GetPlatformDefaultConfig() []byte {
return []byte(
`os:
openCommand: 'cmd /c "start "" {{filename}}"'`)
}

View File

@@ -10,11 +10,11 @@ import (
)
func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
index := gui.getItemPosition(v)
index := gui.getItemPosition(gui.getBranchesView(g))
if index == 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
}
branch := gui.getSelectedBranch(v)
branch := gui.getSelectedBranch(gui.getBranchesView(g))
if err := gui.GitCommand.Checkout(branch.Name, false); err != nil {
gui.createErrorPanel(g, err.Error())
}
@@ -115,16 +115,7 @@ func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch {
}
func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
"space": gui.Tr.SLocalize("checkout"),
"f": gui.Tr.SLocalize("forceCheckout"),
"m": gui.Tr.SLocalize("merge"),
"c": gui.Tr.SLocalize("checkoutByName"),
"n": gui.Tr.SLocalize("newBranch"),
"d": gui.Tr.SLocalize("deleteBranch"),
"D": gui.Tr.SLocalize("forceDeleteBranch"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
})
return gui.renderGlobalOptions(g)
}
// may want to standardise how these select methods work

View File

@@ -1,6 +1,9 @@
package gui
import (
"strconv"
"strings"
"github.com/jesseduffield/gocui"
)
@@ -33,20 +36,6 @@ func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
return gui.switchFocus(g, v, gui.getFilesView(g))
}
func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error {
// resising ahead of time so that the top line doesn't get hidden to make
// room for the cursor on the second line
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer())
if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil {
if err != gocui.ErrUnknownView {
return err
}
}
v.EditNewLine()
return nil
}
func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
message := gui.Tr.TemplateLocalize(
"CloseConfirm",
@@ -57,3 +46,42 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
)
return gui.renderString(g, "options", message)
}
func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
switch {
case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
v.EditDelete(true)
case key == gocui.KeyDelete:
v.EditDelete(false)
case key == gocui.KeyArrowDown:
v.MoveCursor(0, 1, false)
case key == gocui.KeyArrowUp:
v.MoveCursor(0, -1, false)
case key == gocui.KeyArrowLeft:
v.MoveCursor(-1, 0, false)
case key == gocui.KeyArrowRight:
v.MoveCursor(1, 0, false)
case key == gocui.KeyTab:
v.EditNewLine()
case key == gocui.KeySpace:
v.EditWrite(' ')
case key == gocui.KeyInsert:
v.Overwrite = !v.Overwrite
default:
v.EditWrite(ch)
}
gui.RenderCommitLength()
}
func (gui *Gui) getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.Buffer(), "")-1) + " "
}
func (gui *Gui) RenderCommitLength() {
if !gui.Config.GetUserConfig().GetBool("gui.commitLength.show") {
return
}
v := gui.getCommitMessageView(gui.g)
v.Subtitle = gui.getBufferLength(v)
}

View File

@@ -59,13 +59,7 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
}
func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
"s": gui.Tr.SLocalize("squashDown"),
"r": gui.Tr.SLocalize("rename"),
"g": gui.Tr.SLocalize("resetToThisCommit"),
"f": gui.Tr.SLocalize("fixupCommit"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
})
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
@@ -140,10 +134,10 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(v) != 0 {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
}
gui.createPromptPanel(g, v, gui.Tr.SLocalize("RenameCommit"), func(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.RenameCommit(v.Buffer()); err != nil {
return gui.createErrorPanel(g, err.Error())
}
@@ -155,6 +149,19 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
return nil
}
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
}
gui.SubProcess = gui.GitCommand.PrepareCommitAmendSubProcess()
g.Update(func(g *gocui.Gui) error {
return gui.Errors.ErrSubProcess
})
return nil
}
func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) {
v, err := g.View("commits")
if err != nil {

View File

@@ -11,7 +11,6 @@ import (
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error {
@@ -116,20 +115,6 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t
return nil
}
func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error {
// resising ahead of time so that the top line doesn't get hidden to make
// room for the cursor on the second line
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer())
if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil {
if err != gocui.ErrUnknownView {
return err
}
}
v.EditNewLine()
return nil
}
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
actions := gui.Tr.TemplateLocalize(
"CloseConfirm",
@@ -144,9 +129,6 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil {
return err
}
if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, gui.handleNewline); err != nil {
return err
}
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose))
}
@@ -161,17 +143,3 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
coloredMessage := colorFunction(strings.TrimSpace(message))
return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
}
func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
content := utils.TrimTrailingNewline(v.Buffer())
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
vx0, vy0, vx1, vy1 := v.Dimensions()
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
return nil
}
gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel"))
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
return err
}

View File

@@ -7,7 +7,6 @@ import (
// "strings"
"os/exec"
"strings"
"github.com/fatih/color"
@@ -173,31 +172,7 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error {
optionsMap := map[string]string{
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
"S": gui.Tr.SLocalize("stashFiles"),
"c": gui.Tr.SLocalize("CommitChanges"),
"o": gui.Tr.SLocalize("open"),
"i": gui.Tr.SLocalize("ignore"),
"d": gui.Tr.SLocalize("delete"),
"space": gui.Tr.SLocalize("toggleStaged"),
"R": gui.Tr.SLocalize("refresh"),
"t": gui.Tr.SLocalize("addPatch"),
"e": gui.Tr.SLocalize("edit"),
"a": gui.Tr.SLocalize("toggleStagedAll"),
"PgUp/PgDn": gui.Tr.SLocalize("scroll"),
}
if gui.State.HasMergeConflicts {
optionsMap["a"] = gui.Tr.SLocalize("abortMerge")
optionsMap["m"] = gui.Tr.SLocalize("resolveMergeConflicts")
}
if file == nil {
return gui.renderOptionsMap(g, optionsMap)
}
if file.Tracked {
optionsMap["d"] = gui.Tr.SLocalize("checkout")
}
return gui.renderOptionsMap(g, optionsMap)
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
@@ -227,6 +202,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
g.Update(func(g *gocui.Gui) error {
g.SetViewOnTop("commitMessage")
gui.switchFocus(g, filesView, commitMessageView)
gui.RenderCommitLength()
return nil
})
return nil
@@ -250,11 +226,10 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) {
})
}
func (gui *Gui) genericFileOpen(g *gocui.Gui, v *gocui.View, filename string, open func(string) (*exec.Cmd, error)) error {
sub, err := open(filename)
func (gui *Gui) editFile(filename string) error {
sub, err := gui.OSCommand.EditFile(filename)
if err != nil {
return gui.createErrorPanel(g, err.Error())
return gui.createErrorPanel(gui.g, err.Error())
}
if sub != nil {
gui.SubProcess = sub
@@ -268,7 +243,8 @@ func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
if err != nil {
return err
}
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.EditFile)
return gui.editFile(file.Name)
}
func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
@@ -279,22 +255,6 @@ func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
return gui.openFile(file.Name)
}
func (gui *Gui) handleSublimeFileOpen(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.SublimeOpenFile)
}
func (gui *Gui) handleVsCodeFileOpen(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.VsCodeOpenFile)
}
func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles(g)
}

View File

@@ -83,6 +83,7 @@ type guiState struct {
EditHistory *stack.Stack
Platform commands.Platform
Updating bool
Keys []Binding
}
// NewGui builds a new gui handler
@@ -262,6 +263,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
commitMessageView.FgColor = gocui.ColorWhite
commitMessageView.Editable = true
commitMessageView.Editor = gocui.EditorFunc(gui.simpleEditor)
}
}
@@ -307,9 +309,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
gui.resizePopupPanels(g)
return nil
return gui.resizeCurrentPopupPanel(g)
}
func (gui *Gui) promptAnonymousReporting() error {
@@ -347,6 +347,15 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
return nil
}
func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
"PgUp/PgDn": gui.Tr.SLocalize("scroll"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
"esc/q": gui.Tr.SLocalize("close"),
"x": gui.Tr.SLocalize("menu"),
})
}
func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) {
go func() {
for range time.Tick(interval) {
@@ -355,14 +364,6 @@ func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*goc
}()
}
func (gui *Gui) resizePopupPanels(g *gocui.Gui) error {
v := g.CurrentView()
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
return gui.resizePopupPanel(g, v)
}
return nil
}
// Run setup the gui with keybindings and start the mainloop
func (gui *Gui) Run() error {
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
@@ -420,5 +421,10 @@ func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
if gui.State.Updating {
return gui.createUpdateQuitConfirmation(g, v)
}
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
return gui.createConfirmationPanel(g, v, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}, nil)
}
return gocui.ErrQuit
}

View File

@@ -6,77 +6,360 @@ import "github.com/jesseduffield/gocui"
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
ViewName string
Handler func(*gocui.Gui, *gocui.View) error
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
Modifier gocui.Modifier
ViewName string
Handler func(*gocui.Gui, *gocui.View) error
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
Modifier gocui.Modifier
KeyReadable string
Description string
}
func (gui *Gui) keybindings(g *gocui.Gui) error {
func (gui *Gui) GetKeybindings() []Binding {
bindings := []Binding{
{ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: gui.quit},
{ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: gui.quit},
{ViewName: "", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.quit},
{ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: gui.scrollUpMain},
{ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: gui.scrollDownMain},
{ViewName: "", Key: gocui.KeyCtrlU, Modifier: gocui.ModNone, Handler: gui.scrollUpMain},
{ViewName: "", Key: gocui.KeyCtrlD, Modifier: gocui.ModNone, Handler: gui.scrollDownMain},
{ViewName: "", Key: 'P', Modifier: gocui.ModNone, Handler: gui.pushFiles},
{ViewName: "", Key: 'p', Modifier: gocui.ModNone, Handler: gui.pullFiles},
{ViewName: "", Key: 'R', Modifier: gocui.ModNone, Handler: gui.handleRefresh},
{ViewName: "status", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleEditConfig},
{ViewName: "status", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleOpenConfig},
{ViewName: "status", Key: 'u', Modifier: gocui.ModNone, Handler: gui.handleCheckForUpdate},
{ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: gui.handleCommitPress},
{ViewName: "files", Key: 'C', Modifier: gocui.ModNone, Handler: gui.handleCommitEditorPress},
{ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleFilePress},
{ViewName: "files", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleFileRemove},
{ViewName: "files", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleSwitchToMerge},
{ViewName: "files", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleFileEdit},
{ViewName: "files", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleFileOpen},
{ViewName: "files", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleSublimeFileOpen},
{ViewName: "files", Key: 'v', Modifier: gocui.ModNone, Handler: gui.handleVsCodeFileOpen},
{ViewName: "files", Key: 'i', Modifier: gocui.ModNone, Handler: gui.handleIgnoreFile},
{ViewName: "files", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRefreshFiles},
{ViewName: "files", Key: 'S', Modifier: gocui.ModNone, Handler: gui.handleStashSave},
{ViewName: "files", Key: 'A', Modifier: gocui.ModNone, Handler: gui.handleAbortMerge},
{ViewName: "files", Key: 'a', Modifier: gocui.ModNone, Handler: gui.handleStageAll},
{ViewName: "files", Key: 't', Modifier: gocui.ModNone, Handler: gui.handleAddPatch},
{ViewName: "files", Key: 'D', Modifier: gocui.ModNone, Handler: gui.handleResetHard},
{ViewName: "main", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleEscapeMerge},
{ViewName: "main", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handlePickHunk},
{ViewName: "main", Key: 'b', Modifier: gocui.ModNone, Handler: gui.handlePickBothHunks},
{ViewName: "main", Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict},
{ViewName: "main", Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict},
{ViewName: "main", Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.handleSelectTop},
{ViewName: "main", Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.handleSelectBottom},
{ViewName: "main", Key: 'h', Modifier: gocui.ModNone, Handler: gui.handleSelectPrevConflict},
{ViewName: "main", Key: 'l', Modifier: gocui.ModNone, Handler: gui.handleSelectNextConflict},
{ViewName: "main", Key: 'k', Modifier: gocui.ModNone, Handler: gui.handleSelectTop},
{ViewName: "main", Key: 'j', Modifier: gocui.ModNone, Handler: gui.handleSelectBottom},
{ViewName: "main", Key: 'z', Modifier: gocui.ModNone, Handler: gui.handlePopFileSnapshot},
{ViewName: "branches", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleBranchPress},
{ViewName: "branches", Key: 'c', Modifier: gocui.ModNone, Handler: gui.handleCheckoutByName},
{ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: gui.handleForceCheckout},
{ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: gui.handleNewBranch},
{ViewName: "branches", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleDeleteBranch},
{ViewName: "branches", Key: 'D', Modifier: gocui.ModNone, Handler: gui.handleForceDeleteBranch},
{ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleMerge},
{ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleCommitSquashDown},
{ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRenameCommit},
{ViewName: "commits", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleResetToCommit},
{ViewName: "commits", Key: 'f', Modifier: gocui.ModNone, Handler: gui.handleCommitFixup},
{ViewName: "stash", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleStashApply},
{ViewName: "stash", Key: 'g', Modifier: gocui.ModNone, Handler: gui.handleStashPop},
{ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleStashDrop},
{ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: gui.handleCommitConfirm},
{ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleCommitClose},
{ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.handleNewlineCommitMessage},
{
ViewName: "",
Key: 'q',
Modifier: gocui.ModNone,
Handler: gui.quit,
}, {
ViewName: "",
Key: gocui.KeyCtrlC,
Modifier: gocui.ModNone,
Handler: gui.quit,
}, {
ViewName: "",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.quit,
}, {
ViewName: "",
Key: gocui.KeyPgup,
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
}, {
ViewName: "",
Key: gocui.KeyPgdn,
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
}, {
ViewName: "",
Key: gocui.KeyCtrlU,
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
}, {
ViewName: "",
Key: gocui.KeyCtrlD,
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
}, {
ViewName: "",
Key: 'P',
Modifier: gocui.ModNone,
Handler: gui.pushFiles,
Description: gui.Tr.SLocalize("push"),
}, {
ViewName: "",
Key: 'p',
Modifier: gocui.ModNone,
Handler: gui.pullFiles,
Description: gui.Tr.SLocalize("pull"),
}, {
ViewName: "",
Key: 'R',
Modifier: gocui.ModNone,
Handler: gui.handleRefresh,
Description: gui.Tr.SLocalize("refresh"),
}, {
ViewName: "",
Key: 'x',
Modifier: gocui.ModNone,
Handler: gui.handleMenu,
}, {
ViewName: "status",
Key: 'e',
Modifier: gocui.ModNone,
Handler: gui.handleEditConfig,
Description: gui.Tr.SLocalize("EditConfig"),
}, {
ViewName: "status",
Key: 'o',
Modifier: gocui.ModNone,
Handler: gui.handleOpenConfig,
Description: gui.Tr.SLocalize("OpenConfig"),
}, {
ViewName: "status",
Key: 'u',
Modifier: gocui.ModNone,
Handler: gui.handleCheckForUpdate,
Description: gui.Tr.SLocalize("checkForUpdate"),
}, {
ViewName: "files",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleCommitPress,
Description: gui.Tr.SLocalize("CommitChanges"),
}, {
ViewName: "files",
Key: 'C',
Modifier: gocui.ModNone,
Handler: gui.handleCommitEditorPress,
Description: gui.Tr.SLocalize("CommitChangesWithEditor"),
}, {
ViewName: "files",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handleFilePress,
KeyReadable: "space",
Description: gui.Tr.SLocalize("toggleStaged"),
}, {
ViewName: "files",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleFileRemove,
Description: gui.Tr.SLocalize("removeFile"),
}, {
ViewName: "files",
Key: 'm',
Modifier: gocui.ModNone,
Handler: gui.handleSwitchToMerge,
Description: gui.Tr.SLocalize("resolveMergeConflicts"),
}, {
ViewName: "files",
Key: 'e',
Modifier: gocui.ModNone,
Handler: gui.handleFileEdit,
Description: gui.Tr.SLocalize("editFile"),
}, {
ViewName: "files",
Key: 'o',
Modifier: gocui.ModNone,
Handler: gui.handleFileOpen,
Description: gui.Tr.SLocalize("openFile"),
}, {
ViewName: "files",
Key: 'i',
Modifier: gocui.ModNone,
Handler: gui.handleIgnoreFile,
Description: gui.Tr.SLocalize("ignoreFile"),
}, {
ViewName: "files",
Key: 'r',
Modifier: gocui.ModNone,
Handler: gui.handleRefreshFiles,
Description: gui.Tr.SLocalize("refreshFiles"),
}, {
ViewName: "files",
Key: 'S',
Modifier: gocui.ModNone,
Handler: gui.handleStashSave,
Description: gui.Tr.SLocalize("stashFiles"),
}, {
ViewName: "files",
Key: 'A',
Modifier: gocui.ModNone,
Handler: gui.handleAbortMerge,
Description: gui.Tr.SLocalize("abortMerge"),
}, {
ViewName: "files",
Key: 'a',
Modifier: gocui.ModNone,
Handler: gui.handleStageAll,
Description: gui.Tr.SLocalize("toggleStagedAll"),
}, {
ViewName: "files",
Key: 't',
Modifier: gocui.ModNone,
Handler: gui.handleAddPatch,
Description: gui.Tr.SLocalize("addPatch"),
}, {
ViewName: "files",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleResetHard,
Description: gui.Tr.SLocalize("resetHard"),
}, {
ViewName: "main",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleEscapeMerge,
}, {
ViewName: "main",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handlePickHunk,
}, {
ViewName: "main",
Key: 'b',
Modifier: gocui.ModNone,
Handler: gui.handlePickBothHunks,
}, {
ViewName: "main",
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevConflict,
}, {
ViewName: "main",
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
Handler: gui.handleSelectNextConflict,
}, {
ViewName: "main",
Key: gocui.KeyArrowUp,
Modifier: gocui.ModNone,
Handler: gui.handleSelectTop,
}, {
ViewName: "main",
Key: gocui.KeyArrowDown,
Modifier: gocui.ModNone,
Handler: gui.handleSelectBottom,
}, {
ViewName: "main",
Key: 'h',
Modifier: gocui.ModNone,
Handler: gui.handleSelectPrevConflict,
}, {
ViewName: "main",
Key: 'l',
Modifier: gocui.ModNone,
Handler: gui.handleSelectNextConflict,
}, {
ViewName: "main",
Key: 'k',
Modifier: gocui.ModNone,
Handler: gui.handleSelectTop,
}, {
ViewName: "main",
Key: 'j',
Modifier: gocui.ModNone,
Handler: gui.handleSelectBottom,
}, {
ViewName: "main",
Key: 'z',
Modifier: gocui.ModNone,
Handler: gui.handlePopFileSnapshot,
}, {
ViewName: "branches",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handleBranchPress,
KeyReadable: "space",
Description: gui.Tr.SLocalize("checkout"),
}, {
ViewName: "branches",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleCheckoutByName,
Description: gui.Tr.SLocalize("checkoutByName"),
}, {
ViewName: "branches",
Key: 'F',
Modifier: gocui.ModNone,
Handler: gui.handleForceCheckout,
Description: gui.Tr.SLocalize("forceCheckout"),
}, {
ViewName: "branches",
Key: 'n',
Modifier: gocui.ModNone,
Handler: gui.handleNewBranch,
Description: gui.Tr.SLocalize("newBranch"),
}, {
ViewName: "branches",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleDeleteBranch,
Description: gui.Tr.SLocalize("deleteBranch"),
}, {
ViewName: "branches",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleForceDeleteBranch,
Description: gui.Tr.SLocalize("forceDeleteBranch"),
}, {
ViewName: "branches",
Key: 'm',
Modifier: gocui.ModNone,
Handler: gui.handleMerge,
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
}, {
ViewName: "commits",
Key: 's',
Modifier: gocui.ModNone,
Handler: gui.handleCommitSquashDown,
Description: gui.Tr.SLocalize("squashDown"),
}, {
ViewName: "commits",
Key: 'r',
Modifier: gocui.ModNone,
Handler: gui.handleRenameCommit,
Description: gui.Tr.SLocalize("renameCommit"),
}, {
ViewName: "commits",
Key: 'R',
Modifier: gocui.ModNone,
Handler: gui.handleRenameCommitEditor,
Description: gui.Tr.SLocalize("renameCommitEditor"),
}, {
ViewName: "commits",
Key: 'g',
Modifier: gocui.ModNone,
Handler: gui.handleResetToCommit,
Description: gui.Tr.SLocalize("resetToThisCommit"),
}, {
ViewName: "commits",
Key: 'f',
Modifier: gocui.ModNone,
Handler: gui.handleCommitFixup,
Description: gui.Tr.SLocalize("fixupCommit"),
}, {
ViewName: "stash",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handleStashApply,
KeyReadable: "space",
Description: gui.Tr.SLocalize("apply"),
}, {
ViewName: "stash",
Key: 'g',
Modifier: gocui.ModNone,
Handler: gui.handleStashPop,
Description: gui.Tr.SLocalize("pop"),
}, {
ViewName: "stash",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleStashDrop,
Description: gui.Tr.SLocalize("drop"),
}, {
ViewName: "commitMessage",
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
Handler: gui.handleCommitConfirm,
}, {
ViewName: "commitMessage",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleCommitClose,
}, {
ViewName: "menu",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleMenuClose,
}, {
ViewName: "menu",
Key: 'q',
Modifier: gocui.ModNone,
Handler: gui.handleMenuClose,
}, {
ViewName: "menu",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handleMenuPress,
},
}
// Would make these keybindings global but that interferes with editing
// input in the confirmation panel
for _, viewName := range []string{"status", "files", "branches", "commits", "stash"} {
for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} {
bindings = append(bindings, []Binding{
{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView},
@@ -90,6 +373,12 @@ func (gui *Gui) keybindings(g *gocui.Gui) error {
}...)
}
return bindings
}
func (gui *Gui) keybindings(g *gocui.Gui) error {
bindings := gui.GetKeybindings()
for _, binding := range bindings {
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
return err

131
pkg/gui/menu_panel.go Normal file
View File

@@ -0,0 +1,131 @@
package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error {
lineNumber := gui.getItemPosition(v)
if gui.State.Keys[lineNumber].Key == nil {
return nil
}
if len(gui.State.Keys) > lineNumber {
err := gui.handleMenuClose(g, v)
if err != nil {
return err
}
return gui.State.Keys[lineNumber].Handler(g, v)
}
return nil
}
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
// doing nothing for now
// but it is needed for switch in newLineFocused
return nil
}
func (gui *Gui) renderMenuOptions(g *gocui.Gui) error {
optionsMap := map[string]string{
"esc/q": gui.Tr.SLocalize("close"),
"↑ ↓": gui.Tr.SLocalize("navigate"),
"space": gui.Tr.SLocalize("execute"),
}
return gui.renderOptionsMap(g, optionsMap)
}
func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
// better to delete because for example after closing update confirmation panel,
// the focus isn't set back to any of panels and one is unable to even quit
//_, err := g.SetViewOnBottom(v.Name())
err := g.DeleteView("menu")
if err != nil {
return err
}
return gui.returnFocus(g, v)
}
func (gui *Gui) GetKey(binding Binding) string {
r, ok := binding.Key.(rune)
key := ""
if ok {
key = string(r)
} else if binding.KeyReadable != "" {
key = binding.KeyReadable
}
return key
}
func (gui *Gui) GetMaxKeyLength(bindings []Binding) int {
max := 0
for _, binding := range bindings {
keyLength := len(gui.GetKey(binding))
if keyLength > max {
max = keyLength
}
}
return max
}
func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error {
var (
contentGlobal, contentPanel []string
bindingsGlobal, bindingsPanel []Binding
)
// clear keys slice, so we don't have ghost elements
gui.State.Keys = gui.State.Keys[:0]
bindings := gui.GetKeybindings()
padWidth := gui.GetMaxKeyLength(bindings)
for _, binding := range bindings {
key := gui.GetKey(binding)
if key != "" && binding.Description != "" {
content := fmt.Sprintf("%s %s", utils.WithPadding(key, padWidth), binding.Description)
switch binding.ViewName {
case "":
contentGlobal = append(contentGlobal, content)
bindingsGlobal = append(bindingsGlobal, binding)
case v.Name():
contentPanel = append(contentPanel, content)
bindingsPanel = append(bindingsPanel, binding)
}
}
}
// append dummy element to have a separator between
// panel and global keybindings
contentPanel = append(contentPanel, "")
bindingsPanel = append(bindingsPanel, Binding{})
content := append(contentPanel, contentGlobal...)
gui.State.Keys = append(bindingsPanel, bindingsGlobal...)
// append newline at the end so the last line would be selectable
contentJoined := strings.Join(content, "\n") + "\n"
// y1-1 so there will not be an extra space at the end of panel
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined)
menuView, _ := g.SetView("menu", x0, y0, x1, y1-1, 0)
menuView.Title = strings.Title(gui.Tr.SLocalize("menu"))
menuView.FgColor = gocui.ColorWhite
if err := gui.renderMenuOptions(g); err != nil {
return err
}
fmt.Fprint(menuView, contentJoined)
g.Update(func(g *gocui.Gui) error {
_, err := g.SetViewOnTop("menu")
if err != nil {
return err
}
return gui.switchFocus(g, v, menuView)
})
return nil
}

View File

@@ -27,17 +27,13 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry {
if len(gui.State.StashEntries) == 0 {
return nil
}
lineNumber := gui.getItemPosition(v)
stashView, _ := gui.g.View("stash")
lineNumber := gui.getItemPosition(stashView)
return &gui.State.StashEntries[lineNumber]
}
func (gui *Gui) renderStashOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
"space": gui.Tr.SLocalize("apply"),
"g": gui.Tr.SLocalize("pop"),
"d": gui.Tr.SLocalize("drop"),
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
})
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {

View File

@@ -42,11 +42,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
}
func (gui *Gui) renderStatusOptions(g *gocui.Gui) error {
return gui.renderOptionsMap(g, map[string]string{
"o": gui.Tr.SLocalize("OpenConfig"),
"e": gui.Tr.SLocalize("EditConfig"),
"u": gui.Tr.SLocalize("CheckForUpdate"),
})
return gui.renderGlobalOptions(g)
}
func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error {
@@ -76,7 +72,7 @@ func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleEditConfig(g *gocui.Gui, v *gocui.View) error {
filename := gui.Config.GetUserConfig().ConfigFileUsed()
return gui.genericFileOpen(g, v, filename, gui.OSCommand.EditFile)
return gui.editFile(filename)
}
func lazygitTitle() string {

View File

@@ -82,6 +82,8 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
mainView.SetOrigin(0, 0)
switch v.Name() {
case "menu":
return gui.handleMenuSelect(g, v)
case "status":
return gui.handleStatusSelect(g, v)
case "files":
@@ -145,6 +147,7 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
return err
}
g.Cursor = newView.Editable
return gui.newLineFocused(g, newView)
}
@@ -245,6 +248,7 @@ func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) err
}
// TODO: refactor properly
// i'm so sorry but had to add this getBranchesView
func (gui *Gui) getFilesView(g *gocui.Gui) *gocui.View {
v, _ := g.View("files")
return v
@@ -260,6 +264,11 @@ func (gui *Gui) getCommitMessageView(g *gocui.Gui) *gocui.View {
return v
}
func (gui *Gui) getBranchesView(g *gocui.Gui) *gocui.View {
v, _ := g.View("branches")
return v
}
func (gui *Gui) trimmedContent(v *gocui.View) string {
return strings.TrimSpace(v.Buffer())
}
@@ -268,3 +277,25 @@ func (gui *Gui) currentViewName(g *gocui.Gui) string {
currentView := g.CurrentView()
return currentView.Name()
}
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
v := g.CurrentView()
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
return gui.resizePopupPanel(g, v)
}
return nil
}
func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
// If the confirmation panel is already displayed, just resize the width,
// otherwise continue
content := v.Buffer()
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
vx0, vy0, vx1, vy1 := v.Dimensions()
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
return nil
}
gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel"))
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
return err
}

View File

@@ -34,12 +34,24 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitChanges",
Other: "Commit Veranderingen",
}, &i18n.Message{
ID: "CommitChangesWithEditor",
Other: "commit Veranderingen met de git editor",
}, &i18n.Message{
ID: "StatusTitle",
Other: "Status",
}, &i18n.Message{
ID: "GlobalTitle",
Other: "Global",
}, &i18n.Message{
ID: "navigate",
Other: "navigeer",
}, &i18n.Message{
ID: "menu",
Other: "menu",
}, &i18n.Message{
ID: "execute",
Other: "uitvoeren",
}, &i18n.Message{
ID: "stashFiles",
Other: "stash-bestanden",
@@ -66,7 +78,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: "verandering toevoegen",
}, &i18n.Message{
ID: "edit",
Other: "veranderen",
Other: "verander",
}, &i18n.Message{
ID: "scroll",
Other: "scroll",
@@ -172,6 +184,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CloseConfirm",
Other: "{{.keyBindClose}}: Sluiten, {{.keyBindConfirm}}: Bevestigen",
}, &i18n.Message{
ID: "close",
Other: "sluiten",
}, &i18n.Message{
ID: "SureResetThisCommit",
Other: "Weet je het zeker dat je wil resetten naar deze commit?",
@@ -212,8 +227,11 @@ func addDutch(i18nObject *i18n.Bundle) error {
ID: "OnlyRenameTopCommit",
Other: "Je kan alleen de bovenste commit hernoemen",
}, &i18n.Message{
ID: "RenameCommit",
Other: "Hernoem Commit",
ID: "renameCommit",
Other: "hernoem commit",
}, &i18n.Message{
ID: "renameCommitEditor",
Other: "rename commit with editor",
}, &i18n.Message{
ID: "PotentialErrInGetselectedCommit",
Other: "Er is mogelijk een error in getSelected Commit (geen match tussen ui en state)",
@@ -295,6 +313,63 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "MergeAborted",
Other: "Merge afgebroken",
}, &i18n.Message{
ID: "OpenConfig",
Other: "open config file",
}, &i18n.Message{
ID: "EditConfig",
Other: "verander config file",
}, &i18n.Message{
ID: "ForcePush",
Other: "Forceer push",
}, &i18n.Message{
ID: "ForcePushPrompt",
Other: "Jou branch is afgeweken van de remote branch. Druk 'esc' om te anuleren, of 'enter' om geforceert te pushen.",
}, &i18n.Message{
ID: "checkForUpdate",
Other: "check voor updates",
}, &i18n.Message{
ID: "CheckingForUpdates",
Other: "checken voor updates...",
}, &i18n.Message{
ID: "OnLatestVersionErr",
Other: "Je hebt al de laatste versie",
}, &i18n.Message{
ID: "MajorVersionErr",
Other: "Nieuwe versie ({{.newVersion}}) is niet teruggaand compatibele vergeleken met de huidige versie ({{.currentVersion}})",
}, &i18n.Message{
ID: "CouldNotFindBinaryErr",
Other: "Kon geen binary vinden op {{.url}}",
}, &i18n.Message{
ID: "AnonymousReportingTitle",
Other: "Help maak lazygit beter",
}, &i18n.Message{
ID: "AnonymousReportingPrompt",
Other: "Zou je anonieme data rapportage willen aanzetten om lazygit beter te kunnen maken? (enter/esc)",
}, &i18n.Message{
ID: "removeFile",
Other: `Verwijder als untracked / uitchecken wordt gevolgd (ga weg)`,
}, &i18n.Message{
ID: "editFile",
Other: `verander bestand`,
}, &i18n.Message{
ID: "openFile",
Other: `open bestand`,
}, &i18n.Message{
ID: "ignoreFile",
Other: `voeg toe aan .gitignore`,
}, &i18n.Message{
ID: "refreshFiles",
Other: `refresh bestanden`,
}, &i18n.Message{
ID: "resetHard",
Other: `harde reset`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `merge in met huidige checked out branch`,
}, &i18n.Message{
ID: "ConfirmQuit",
Other: `Weet je zeker dat je dit programma wil sluiten?`,
},
)
}

View File

@@ -42,12 +42,24 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitChanges",
Other: "commit changes",
}, &i18n.Message{
ID: "CommitChangesWithEditor",
Other: "commit changes using git editor",
}, &i18n.Message{
ID: "StatusTitle",
Other: "Status",
}, &i18n.Message{
ID: "GlobalTitle",
Other: "Global",
}, &i18n.Message{
ID: "navigate",
Other: "navigate",
}, &i18n.Message{
ID: "menu",
Other: "menu",
}, &i18n.Message{
ID: "execute",
Other: "execute",
}, &i18n.Message{
ID: "stashFiles",
Other: "stash files",
@@ -69,6 +81,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "refresh",
Other: "refresh",
}, &i18n.Message{
ID: "push",
Other: "push",
}, &i18n.Message{
ID: "pull",
Other: "pull",
}, &i18n.Message{
ID: "addPatch",
Other: "add patch",
@@ -183,6 +201,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CloseConfirm",
Other: "{{.keyBindClose}}: close, {{.keyBindConfirm}}: confirm",
}, &i18n.Message{
ID: "close",
Other: "close",
}, &i18n.Message{
ID: "SureResetThisCommit",
Other: "Are you sure you want to reset to this commit?",
@@ -223,8 +244,11 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "OnlyRenameTopCommit",
Other: "Can only rename topmost commit",
}, &i18n.Message{
ID: "RenameCommit",
Other: "Rename Commit",
ID: "renameCommit",
Other: "rename commit",
}, &i18n.Message{
ID: "renameCommitEditor",
Other: "rename commit with editor",
}, &i18n.Message{
ID: "PotentialErrInGetselectedCommit",
Other: "potential error in getSelected Commit (mismatched ui and state)",
@@ -319,8 +343,8 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "ForcePushPrompt",
Other: "Your branch has diverged from the remote branch. Press 'esc' to cancel, or 'enter' to force push.",
}, &i18n.Message{
ID: "CheckForUpdate",
Other: "Check for update",
ID: "checkForUpdate",
Other: "check for update",
}, &i18n.Message{
ID: "CheckingForUpdates",
Other: "Checking for updates...",
@@ -329,7 +353,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: "You already have the latest version",
}, &i18n.Message{
ID: "MajorVersionErr",
Other: "New version has non-backwards compatible changes.",
Other: "New version ({{.newVersion}}) has non-backwards compatible changes compared to the current version ({{.currentVersion}})",
}, &i18n.Message{
ID: "CouldNotFindBinaryErr",
Other: "Could not find any binary at {{.url}}",
@@ -342,6 +366,30 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "GitconfigParseErr",
Other: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`,
}, &i18n.Message{
ID: "removeFile",
Other: `delete if untracked / checkout if tracked`,
}, &i18n.Message{
ID: "editFile",
Other: `edit file`,
}, &i18n.Message{
ID: "openFile",
Other: `open file`,
}, &i18n.Message{
ID: "ignoreFile",
Other: `add to .gitignore`,
}, &i18n.Message{
ID: "refreshFiles",
Other: `refresh files`,
}, &i18n.Message{
ID: "resetHard",
Other: `reset hard`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `merge into currently checked out branch`,
}, &i18n.Message{
ID: "ConfirmQuit",
Other: `Are you sure you want to quit?`,
},
)
}

View File

@@ -32,12 +32,24 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitChanges",
Other: "commituj zmiany",
}, &i18n.Message{
ID: "CommitChangesWithEditor",
Other: "commituj zmiany używając edytora z gita",
}, &i18n.Message{
ID: "StatusTitle",
Other: "Status",
}, &i18n.Message{
ID: "GlobalTitle",
Other: "Globalne",
}, &i18n.Message{
ID: "navigate",
Other: "nawiguj",
}, &i18n.Message{
ID: "menu",
Other: "menu",
}, &i18n.Message{
ID: "execute",
Other: "wykonaj",
}, &i18n.Message{
ID: "stashFiles",
Other: "przechowaj pliki",
@@ -134,6 +146,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "DeleteBranchMessage",
Other: "Jesteś pewien, że chcesz usunąć gałąź {{.selectedBranchName}} ?",
}, &i18n.Message{
ID: "ForceDeleteBranchMessage",
Other: "Na pewno wymusić usunięcie gałęzi {{.selectedBranchName}}?",
}, &i18n.Message{
ID: "CantMergeBranchIntoItself",
Other: "Nie możesz scalić gałęzi do samej siebie",
@@ -152,6 +167,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "deleteBranch",
Other: "usuń gałąź",
}, &i18n.Message{
ID: "forceDeleteBranch",
Other: "usuń gałąź (wymuś)",
}, &i18n.Message{
ID: "NoBranchesThisRepo",
Other: "Brak gałęzi dla tego repozytorium",
@@ -164,6 +182,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CloseConfirm",
Other: "{{.keyBindClose}}: zamknij, {{.keyBindConfirm}}: potwierdź",
}, &i18n.Message{
ID: "close",
Other: "zamknij",
}, &i18n.Message{
ID: "SureResetThisCommit",
Other: "Jesteś pewny, że chcesz zresetować ten commit?",
@@ -204,8 +225,11 @@ func addPolish(i18nObject *i18n.Bundle) error {
ID: "OnlyRenameTopCommit",
Other: "Można przmianować tylko najwyższy commit",
}, &i18n.Message{
ID: "RenameCommit",
Other: "Przemianuj commit",
ID: "renameCommit",
Other: "przemianuj commit",
}, &i18n.Message{
ID: "renameCommitEditor",
Other: "przemianuj commit w edytorze",
}, &i18n.Message{
ID: "PotentialErrInGetselectedCommit",
Other: "potencjalny błąd w getSelected Commit (niedopasowane ui i stan)",
@@ -287,6 +311,63 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "MergeAborted",
Other: "Scalanie anulowane",
}, &i18n.Message{
ID: "OpenConfig",
Other: "otwórz plik konfiguracyjny",
}, &i18n.Message{
ID: "EditConfig",
Other: "edytuj plik konfiguracyjny",
}, &i18n.Message{
ID: "ForcePush",
Other: "Wymuś wypchnięcie",
}, &i18n.Message{
ID: "ForcePushPrompt",
Other: "Twoja gałąź rozeszła się z gałęzią zdalną. Wciśnij 'esc' aby anulować lub 'enter' aby wymusić wypchnięcie.",
}, &i18n.Message{
ID: "checkForUpdate",
Other: "sprawdź aktualizacje",
}, &i18n.Message{
ID: "CheckingForUpdates",
Other: "Sprawdzanie aktualizacji...",
}, &i18n.Message{
ID: "OnLatestVersionErr",
Other: "Już posiadasz najnowszą wersję",
}, &i18n.Message{
ID: "MajorVersionErr",
Other: "Nowa wersja ({{.newVersion}}) posiada niekompatybilne zmiany w porównaniu do obecnej wersji ({{.currentVersion}})",
}, &i18n.Message{
ID: "CouldNotFindBinaryErr",
Other: "Nie można znaleźć pliku binarnego w {{.url}}",
}, &i18n.Message{
ID: "AnonymousReportingTitle",
Other: "Help make lazygit better",
}, &i18n.Message{
ID: "AnonymousReportingPrompt",
Other: "Włączyć anonimowe raportowanie błędów w celu pomocy w usprawnianiu lazygita (enter/esc)?",
}, &i18n.Message{
ID: "removeFile",
Other: `usuń jeśli nie śledzony / przełącz jeśli śledzony`,
}, &i18n.Message{
ID: "editFile",
Other: `edytuj plik`,
}, &i18n.Message{
ID: "openFile",
Other: `otwórz plik`,
}, &i18n.Message{
ID: "ignoreFile",
Other: `dodaj do .gitignore`,
}, &i18n.Message{
ID: "refreshFiles",
Other: `odśwież pliki`,
}, &i18n.Message{
ID: "resetHard",
Other: `zresetuj twardo`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `scal do obecnej gałęzi`,
}, &i18n.Message{
ID: "ConfirmQuit",
Other: `Na pewno chcesz wyjść z programu?`,
},
)
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/sirupsen/logrus"
)
// Update checks for updates and does updates
// Updater checks for updates and does updates
type Updater struct {
Log *logrus.Entry
Config config.AppConfigurer
@@ -30,7 +30,7 @@ type Updater struct {
Tr *i18n.Localizer
}
// Updater implements the check and update methods
// Updaterer implements the check and update methods
type Updaterer interface {
CheckForNewUpdate()
Update()
@@ -78,6 +78,7 @@ func (u *Updater) getLatestVersionNumber() (string, error) {
return dat["tag_name"].(string), nil
}
// RecordLastUpdateCheck records last time an update check was performed
func (u *Updater) RecordLastUpdateCheck() error {
u.Config.GetAppState().LastUpdateCheck = time.Now().Unix()
return u.Config.SaveAppState()
@@ -88,11 +89,14 @@ func (u *Updater) majorVersionDiffers(oldVersion, newVersion string) bool {
if oldVersion == "unversioned" {
return false
}
oldVersion = strings.TrimPrefix(oldVersion, "v")
newVersion = strings.TrimPrefix(newVersion, "v")
return strings.Split(oldVersion, ".")[0] != strings.Split(newVersion, ".")[0]
}
func (u *Updater) checkForNewUpdate() (string, error) {
u.Log.Info("Checking for an updated version")
currentVersion := u.Config.GetVersion()
if err := u.RecordLastUpdateCheck(); err != nil {
return "", err
}
@@ -101,15 +105,22 @@ func (u *Updater) checkForNewUpdate() (string, error) {
if err != nil {
return "", err
}
u.Log.Info("Current version is " + u.Config.GetVersion())
u.Log.Info("Current version is " + currentVersion)
u.Log.Info("New version is " + newVersion)
if newVersion == u.Config.GetVersion() {
if newVersion == currentVersion {
return "", errors.New(u.Tr.SLocalize("OnLatestVersionErr"))
}
if u.majorVersionDiffers(u.Config.GetVersion(), newVersion) {
return "", errors.New(u.Tr.SLocalize("MajorVersionErr"))
if u.majorVersionDiffers(currentVersion, newVersion) {
errMessage := u.Tr.TemplateLocalize(
"MajorVersionErr",
i18n.Teml{
"newVersion": newVersion,
"currentVersion": currentVersion,
},
)
return "", errors.New(errMessage)
}
rawUrl, err := u.getBinaryUrl(newVersion)

View File

@@ -83,6 +83,7 @@ func GetProjectRoot() string {
return strings.Split(dir, "lazygit")[0] + "lazygit"
}
// Loader dumps a string to be displayed as a loader
func Loader() string {
characters := "|/-\\"
now := time.Now()
@@ -90,3 +91,11 @@ func Loader() string {
index := nanos / 50000000 % int64(len(characters))
return characters[index : index+1]
}
// ResolvePlaceholderString populates a template with values
func ResolvePlaceholderString(str string, arguments map[string]string) string {
for key, value := range arguments {
str = strings.Replace(str, "{{"+key+"}}", value, -1)
}
return str
}

View File

@@ -114,3 +114,56 @@ func TestNormalizeLinefeeds(t *testing.T) {
assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray)))
}
}
func TestResolvePlaceholderString(t *testing.T) {
type scenario struct {
templateString string
arguments map[string]string
expected string
}
scenarios := []scenario{
{
"",
map[string]string{},
"",
},
{
"hello",
map[string]string{},
"hello",
},
{
"hello {{arg}}",
map[string]string{},
"hello {{arg}}",
},
{
"hello {{arg}}",
map[string]string{"arg": "there"},
"hello there",
},
{
"hello",
map[string]string{"arg": "there"},
"hello",
},
{
"{{nothing}}",
map[string]string{"nothing": ""},
"",
},
{
"{{}} {{ this }} { should not throw}} an {{{{}}}} error",
map[string]string{
"blah": "blah",
"this": "won't match",
},
"{{}} {{ this }} { should not throw}} an {{{{}}}} error",
},
}
for _, s := range scenarios {
assert.EqualValues(t, string(s.expected), ResolvePlaceholderString(s.templateString, s.arguments))
}
}

View File

@@ -0,0 +1,54 @@
// run:
// LANG=en go run generate_cheatsheet.go
// to generate Keybindings_en.md file in current directory
// change LANG to generate cheatsheet in different language (if supported)
package main
import (
"fmt"
"os"
"strings"
"github.com/jesseduffield/lazygit/pkg/app"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func main() {
appConfig, _ := config.NewAppConfig("", "", "", "", "", new(bool))
a, _ := app.NewApp(appConfig)
lang := a.Tr.GetLanguage()
name := "Keybindings_" + lang + ".md"
bindings := a.Gui.GetKeybindings()
padWidth := a.Gui.GetMaxKeyLength(bindings)
file, _ := os.Create(name)
current := "v"
content := ""
title := ""
file.WriteString("# Lazygit " + a.Tr.SLocalize("menu"))
for _, binding := range bindings {
if key := a.Gui.GetKey(binding); key != "" && (binding.Description != "" || key == "x") {
if binding.ViewName != current {
current = binding.ViewName
if current == "" {
title = a.Tr.SLocalize("GlobalTitle")
} else {
title = a.Tr.SLocalize(strings.Title(current) + "Title")
}
content = fmt.Sprintf("</pre>\n\n## %s\n<pre>\n", title)
file.WriteString(content)
}
// workaround to include menu keybinding in cheatsheet
// could not add this Description field directly to keybindings.go,
// because then menu key would be displayed in menu itself and that is undesirable
if key == "x" {
binding.Description = a.Tr.SLocalize("menu")
}
content = fmt.Sprintf("\t<kbd>%s</kbd>%s %s\n", key, strings.TrimPrefix(utils.WithPadding(key, padWidth), key), binding.Description)
file.WriteString(content)
}
}
}

View File

@@ -3,7 +3,7 @@
set -e
echo "" > coverage.txt
for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" -type d); do
for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" ! -path "./scripts*" -type d); do
if ls $d/*.go &> /dev/null; then
go test -v -race -coverprofile=profile.out -covermode=atomic $d
if [ -f profile.out ]; then

View File

@@ -114,8 +114,8 @@ func (v *View) EditDelete(back bool) {
func (v *View) EditNewLine() {
v.breakLine(v.cx, v.cy)
v.ox = 0
v.cy = v.cy + 1
v.cx = 0
v.MoveCursor(0, 1, true)
}
// MoveCursor moves the cursor taking into account the width of the line/view,

View File

@@ -476,6 +476,11 @@ func (g *Gui) flush() error {
return err
}
}
if v.Subtitle != "" {
if err := g.drawSubtitle(v, fgColor, bgColor); err != nil {
return err
}
}
}
if err := g.draw(v); err != nil {
return err
@@ -582,6 +587,25 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
return nil
}
// drawSubtitle draws the subtitle of the view.
func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
if v.y0 < 0 || v.y0 >= g.maxY {
return nil
}
start := v.x1 - 5 - len(v.Subtitle)
for i, ch := range v.Subtitle {
x := start + i
if x >= v.x1 {
break
}
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
return err
}
}
return nil
}
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.Cursor {

View File

@@ -77,6 +77,9 @@ type View struct {
// If Frame is true, Title allows to configure a title for the view.
Title string
// If Frame is true, Subtitle allows to configure a subtitle for the view.
Subtitle string
// If Mask is true, the View will display the mask instead of the real
// content
Mask rune