Compare commits

...
78 Commits
Author SHA1 Message Date
Jesse Duffield 2ca029297e Merge branch 'master' of https://github.com/jesseduffield/lazygit 2019-06-24 12:29:59 +10:00
Jesse Duffield c107eed890 emergency situation: we're not logging to rollrus while we're past the request quota 2019-06-24 12:29:44 +10:00
Jesse Duffield e74709fecd emergency situation: we're not logging to rollrus while we're past the request quota 2019-06-24 12:25:35 +10:00
Jesse Duffield abddea060e revert menu panel error panel usage 2019-06-08 20:29:25 +10:00
Jesse Duffield 3e40369fd2 add GIT_OPTIONAL_LOCKS=0 env var to all commands 2019-06-06 20:53:35 +10:00
Jesse Duffield 0f0fda1660 allow stashing staged changes
reinstate old stash functionality with the 's' keybinding
2019-06-06 20:50:19 +10:00
Jesse Duffield bd2170a99c request explicit return from subprocess
Previously we were recording output from subprocesses using a multiwriter
and hooking that up to the cmd's stdout to write to both os.Stdout and
a buffer. We would then display the output after the program finished.

This worked well for commands like 'ls' but not for commands like 'vi'
which expect you to be in a tty, and when you've got the cmd's stdout
pointing at a multiwriter, the subprogram thinks we're not in a tty
and then things like terminal corruption can happen. This was the case
with neovim, and even in vim a warning was given with a pause before
starting the program.

Now we're chucking out the multiwriter and instead making it that you
need to press enter after the program has finished to return to lazygit.
This allows you to view the output of the program (e.g. if it's ls) and
then decide that you want to return. It's one level of unnecessary
redirection for editors like vim, but even they could potentially have
output to stderr/stdout that you want to look at before returning.

 Please enter the commit message for your changes. Lines starting
2019-05-26 21:19:54 +10:00
Jesse Duffield c039e5bed0 support going to start/end of line and deleting lines in simple editor 2019-05-26 12:42:17 +10:00
Jesse Duffield 527c025a0c use shift+j/k to scroll main, ctrl+j/k to move commits 2019-05-25 16:48:17 +10:00
Jesse Duffield 53cded77f1 fix padding with coloures strings 2019-05-19 15:25:33 +10:00
Jesse Duffield 4a4dc676fc simplify code for logging output of subprocess 2019-05-18 11:30:10 +10:00
Jesse Duffield c61bfbdd4c Support opening lazygit in a submodule 2019-05-12 17:59:49 +10:00
Suhas Karanth e38d9d5f22 Add alternatives for scroll actions to context map 2019-05-12 16:20:42 +10:00
Suhas Karanth 97f060d38d Add field Alternative to gui.Binding
Document and use alternative keybinding for generating cheatsheet. Add
alt keybinding fn+up/down for scroll up/down actions.

Also run `go run scripts/generate_cheatsheet.go`.
2019-05-12 16:20:42 +10:00
Jesse Duffield 357b8fa98f Bump gocui fork 2019-05-09 21:27:35 +10:00
mjarkk 8754d766e2 Made not enough space pannel looks better on 1 height 2019-05-07 08:47:41 +02:00
mjarkk 2388c3ee9a Fixed some sugestions from jesseduffield 2019-05-06 20:04:54 +02:00
Mark Kopenga 61890cb9de Merge branch 'master' into smaller-ui 2019-05-06 15:24:36 +02:00
Jesse Duffield 5a0d0bb299 support resetting to a commit in either soft, hard, or mixed mode 2019-05-06 22:44:38 +10:00
Jesse Duffield 2746b1bd38 Prevent crash when opening in small window
We were crashing when opening lazygit in a small window because the limit view
was the only view that got created, and there were two functions that referenced
either the 'current' view or the files view, neither of which existed.

Now those functions just return nil if the view does not exist
2019-05-06 22:39:35 +10:00
Suhas Karanth e09aac6450 Improve directory check for .git
Return error if the .git exists but is not a directory. This provides a
slightly better failure message for git repo with submodules in case
the '.git' is a file which provides the reference to the parent's .git
folder with the submodule inside.
2019-05-06 21:37:42 +10:00
mjarkk 19a6368377 Changed the way how the view height are set 2019-05-05 15:57:35 +02:00
mjarkk e6122122e9 Updated the gocui package 2019-05-05 11:50:51 +02:00
mjarkk 492614ebc7 Made the ui even smaller 2019-04-26 08:24:14 +02:00
Mark Kopenga d31f0ed39b Merge branch 'master' into smaller-ui 2019-04-26 07:46:45 +02:00
mjarkk b505c295d2 Fixed another view things 2019-04-26 13:44:37 +10:00
mjarkk 0b9d7edd47 Fixed sugestions 2019-04-26 13:44:37 +10:00
mjarkk e9fbb608a8 Translated missing sentences 2019-04-26 13:44:37 +10:00
mjarkk 6ba05c94ea Added another resizing step 2019-04-25 21:37:19 +02:00
mjarkk 07fec6d00e Made the go bot happy 2019-04-20 16:51:50 +02:00
mjarkk a69b985086 Better UI on small screens 2019-04-20 15:56:23 +02:00
Jesse Duffield 471733fe03 add english translations to dutch/polish i18n files for translation later 2019-04-13 14:39:46 +10:00
Jesse Duffield 0d3a193ab5 Add 'w' keybinding in files panel to commit as a WIP
If your git.skipHookPrefix is set to, say, WIP, in your config, then
hitting 'w' in the files panel will bring up the commit message panel
with 'WIP' pre-filled, so you just need to hit enter to confirm
(or add some more to the message) in order to commit your changes
with the --no-verify flag, meaning the pre-commit hook will be skipped
2019-04-13 14:38:17 +10:00
Jesse Duffield ab9fa291a8 Add skipHookPrefix to config
allows a user to specify a commit message prefix that will tell lazygit to skip
the pre-commit hook. This defaults to WIP. Setting it to the empty string will
disable the feature.

So if my message goes 'WIP: do the thing' then the pre-commit hook will not run
2019-04-13 14:38:17 +10:00
Jesse Duffield cadc74eeec Update issue templates 2019-04-13 12:35:09 +10:00
Peter Lundberg fc3a57b5e2 Change expected sha for DiscardOldFileChanges 2019-04-10 17:17:31 +10:00
Peter Lundberg 7ff07e1454 Always include atleast 2 commits when doing squash and fixup 2019-04-10 17:17:31 +10:00
Jesse Duffield 3e779bca8d bump gocui to fix invalid point crashing issue 2019-04-10 10:03:35 +10:00
Jesse Duffield 0f1abcb10c remove subprocess channel stuff 2019-04-07 17:15:01 +10:00
Jesse Duffield 55538a3695 support custom commands 2019-04-07 17:15:01 +10:00
Jesse Duffield 878a15aff4 remove verbose flag from go test 2019-04-07 13:13:40 +10:00
Jesse Duffield 60e33f5d8c Allow for creating fixup! commits 2019-04-07 13:13:40 +10:00
Claudia b422692746 Remove custom Homebrew tap from instructions
Now that lazygit has been added to the Homebrew core repository
([1] [2]), Homebrew users no longer need to tap into
`jesseduffield/lazygit` to install it.

[1]: https://github.com/jesseduffield/lazygit/issues/256

[2]: https://github.com/Homebrew/homebrew-core/pull/37614
2019-04-06 18:33:27 +11:00
skanehira f34be1896a fixed some #397 2019-04-06 13:02:20 +11:00
skanehira c350cdba43 add feature of display diff between specific commits #397 2019-04-06 13:02:20 +11:00
Jesse Duffield 1a933eaa73 pass length of options to createMenu 2019-03-23 13:26:17 +11:00
Jesse Duffield bd46e9c5b0 delete menu keybinding before setting new one 2019-03-23 13:26:17 +11:00
Jesse Duffield 09b7ae21bc always attempt to discard changes from current file 2019-03-23 13:26:17 +11:00
Jesse Duffield acfc961909 move soft reset keybinding into reset options 2019-03-23 13:26:17 +11:00
Jesse Duffield f502f75e1f add more options for resetting files in the working tree 2019-03-23 13:26:17 +11:00
Jesse Duffield ff97ef7b94 support discarding unstaged changes 2019-03-23 13:26:17 +11:00
Jesse Duffield a2c780b085 retain commit message if precommit hook fails 2019-03-23 13:07:36 +11:00
Jesse Duffield b99305c909 push codecov result before compiling on all platforms 2019-03-23 12:00:43 +11:00
Jesse Duffield d84dfc23e7 Rely on model rather than view to focus a point
Currently when we want to focus a point on a view (i.e. highlight a
line and ensure it's within the bounds of a view's box, we use the
LinesHeight method on the view to work out how many lines in total
there are.

This is bad because for example if we come back from editing a file,
the view will have no contents so LinesHeight == 0, but we might
be trying to select line 10 because there are actual ten things we
expect to be rendered already. This causes a crash when e.g. 10 is
greater than the height of the view.

So we need to pass in to our FocusPoint method the actual number of
items we want to render, rather than having the method rely on the
LinesHeight, so that the method knows to scroll a bit before setting
the cursor's y position.

Unfortunately this makes for some awkward code with our current setup.
We don't have a good interface type on these state objects so we now
need to explicitly obtain the len() of whatever array we're rendering.

In the case of the menu panel this is even more awkward because the items
list is just an interface{} and it's not easy to get the list of that, so
now when we instantiate a menu we need to pass in the count of items
as well.

The better solution would be to define an interface with a getItems
and getLength method and have all these item arrays become structs
implementing the interface, but I am too lazy to do this right now :)
2019-03-23 11:54:25 +11:00
Jesse Duffield 9d8fd3594e remove go modules
Perhaps one day we'll revisit this, but right now supporting go modules is just a headache.
Dep does everything we need and it's really easy to work with, and given that supporting both simultaneously is too cumbersome, and I'm too lazy to make the switch to go modules properly, I'm sticking with go dep for now.
2019-03-22 21:28:25 +11:00
Jesse Duffield e68dbeb7eb organise keybindings better 2019-03-22 20:20:06 +11:00
skanehira 32ddf0c296 generate commit files keybind 2019-03-18 09:49:23 +11:00
skanehira c453bfeb32 generate the cheatsheet for each supported language 2019-03-18 09:49:23 +11:00
skanehira f6ca450d36 add commit files keybind to Keybindings_en.md 2019-03-18 09:49:23 +11:00
Jesse Duffield d5f617ec92 show some more errors in the gui rather than panicking 2019-03-16 12:51:48 +11:00
Jesse Duffield 6d104bfa91 show file remove error in gui rather than panic 2019-03-16 12:51:48 +11:00
Jesse Duffield e583cc2519 allow autostashing changes when checking out a branch 2019-03-16 12:51:48 +11:00
Kristijan Husak 0d208b7957 Update bitbucket pull request url. 2019-03-16 11:58:26 +11:00
Jesse Duffield 43e5c042a2 prompt user to git init when outside a repo 2019-03-16 11:38:16 +11:00
Jesse Duffield 39844ffef9 allow a LOG_LEVEL env var to be set 2019-03-16 10:52:30 +11:00
Jesse Duffield f5c8aac97d add two more tests 2019-03-16 10:20:27 +11:00
Jesse Duffield b6447ebdbb allow adding a file viewed from the commit files panel 2019-03-16 10:20:27 +11:00
Jesse Duffield 466fc4227e fix tests 2019-03-16 10:20:27 +11:00
Jesse Duffield c034c88be4 display test name when running tests 2019-03-16 10:20:27 +11:00
Jesse Duffield 72830efc45 add some tests 2019-03-16 10:20:27 +11:00
Jesse Duffield c98eddc185 appease golangci 2019-03-16 10:20:27 +11:00
Jesse Duffield 3b2353b5ae remove redundant call to refreshCommitFilesView
We already call this function inside the refreshCommitsView function.
We call it there because it's logical that A) one occurs whenever the other does and
B) the commit files only get refreshed after we've updated the commits themselves
2019-03-16 10:20:27 +11:00
Jesse Duffield 3f567c952c i18n for error message about a feature being disabled for GPG users 2019-03-16 10:20:27 +11:00
Jesse Duffield 4f7f6a073c allow user to discard old file changes for a given commit 2019-03-16 10:20:27 +11:00
Jesse Duffield 0e008cc15f allow user to checkout old files 2019-03-16 10:20:27 +11:00
Jesse Duffield 1ad9c6faac minor cleanup 2019-03-16 10:20:27 +11:00
skanehira 06fe726ee7 Add feature of display committed file list #383 2019-03-16 10:20:27 +11:00
skanehira 1b6e46973e remove the -o option from Dockerfile 2019-03-08 15:51:23 +11:00
54 changed files with 2719 additions and 729 deletions
+4 -17
View File
@@ -6,19 +6,6 @@ jobs:
working_directory: /go/src/github.com/jesseduffield/lazygit
steps:
- checkout
- 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: |
@@ -33,15 +20,15 @@ jobs:
name: Run tests
command: |
./test.sh
- run:
name: Push on codecov result
command: |
bash <(curl -s https://codecov.io/bash)
- run:
name: Compile project on every platform
command: |
go get github.com/mitchellh/gox
gox -parallel 10 -os "linux freebsd netbsd windows" -osarch "darwin/i386 darwin/amd64"
- run:
name: Push on codecov result
command: |
bash <(curl -s https://codecov.io/bash)
- save_cache:
key: pkg-cache-{{ checksum "Gopkg.lock" }}-v4
paths:
+3
View File
@@ -1,6 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
+14
View File
@@ -0,0 +1,14 @@
---
name: Discussion
about: Begin a discussion
title: ''
labels: discussion
assignees: ''
---
**Topic**
A clear and concise description of what you want to discuss
**Your thoughts**
What you have to say about the topic
@@ -1,6 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
+1 -1
View File
@@ -5,7 +5,7 @@
FROM golang:alpine
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY ./ .
RUN CGO_ENABLED=0 GOOS=linux go build -o lazygit .
RUN CGO_ENABLED=0 GOOS=linux go build
FROM alpine:latest
RUN apk add -U git xdg-utils
Generated
+2 -2
View File
@@ -197,11 +197,11 @@
[[projects]]
branch = "master"
digest = "1:831819e8726b6b19e90079bce76d3048e2be3595359ee912d63d560e0db89b97"
digest = "1:98610085008f8593244c81f802d885acf4302093dd3e37737fc6543ae86ce471"
name = "github.com/jesseduffield/gocui"
packages = ["."]
pruneopts = "NUT"
revision = "f0f0ab442e6c8e42650a7e38a45122bba2e11f3f"
revision = "625842035f050c8af384f639da328c6a0a0e4ca3"
[[projects]]
branch = "master"
+2 -3
View File
@@ -10,7 +10,7 @@ Jira? This is the app for you!
- [Installation](https://github.com/jesseduffield/lazygit#installation)
- [Usage](https://github.com/jesseduffield/lazygit#usage),
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings_en.md)
[Keybindings](/docs/keybindings)
- [Cool Features](https://github.com/jesseduffield/lazygit#cool-features)
- [Contributing](https://github.com/jesseduffield/lazygit#contributing)
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
@@ -21,7 +21,6 @@ Jira? This is the app for you!
### Homebrew
```sh
brew tap jesseduffield/lazygit
brew install lazygit
```
@@ -104,7 +103,7 @@ whichever rc file you're using).
- Basic video tutorial [here](https://youtu.be/VDXvbHZYeKY).
- List of keybindings
[here](/docs/Keybindings_en.md).
[here](/docs/keybindings).
## Cool features
+1
View File
@@ -21,6 +21,7 @@
merging:
# only applicable to unix users
manualCommit: false
skipHookPrefix: WIP
update:
method: prompt # can be: prompt | background | never
days: 14 # how often an update is checked for
@@ -22,21 +22,22 @@
<pre>
<kbd>c</kbd>: commit changes
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>A</kbd>: amend last commit
<kbd>C</kbd>: commit changes using git editor
<kbd>space</kbd>: toggle staged
<kbd>d</kbd>: delete if untracked / checkout if tracked
<kbd>d</kbd>: view 'discard changes' options
<kbd>e</kbd>: edit file
<kbd>o</kbd>: open file
<kbd>i</kbd>: add to .gitignore
<kbd>r</kbd>: refresh files
<kbd>S</kbd>: stash files
<kbd>s</kbd>: soft reset to last commit
<kbd>a</kbd>: stage/unstage all
<kbd>t</kbd>: add patch
<kbd>D</kbd>: reset hard and remove untracked files
<kbd>D</kbd>: view reset options
<kbd>enter</kbd>: stage individual hunks/lines
<kbd>f</kbd>: fetch
<kbd>X</kbd>: execute custom command
</pre>
## Branches
@@ -61,9 +62,11 @@
<kbd>R</kbd>: rename commit with editor
<kbd>g</kbd>: reset to this commit
<kbd>f</kbd>: fixup commit
<kbd>F</kbd>: create fixup commit for this commit
<kbd>S</kbd>: squash above commits
<kbd>d</kbd>: delete commit
<kbd>J</kbd>: move commit down one
<kbd>K</kbd>: move commit up one
<kbd>ctrl+j</kbd>: move commit down one
<kbd>ctrl+k</kbd>: move commit up one
<kbd>e</kbd>: edit commit
<kbd>A</kbd>: amend commit with staged changes
<kbd>p</kbd>: pick commit (when mid-rebase)
@@ -71,6 +74,8 @@
<kbd>c</kbd>: copy commit (cherry-pick)
<kbd>C</kbd>: copy commit range (cherry-pick)
<kbd>v</kbd>: paste commits (cherry-pick)
<kbd>enter</kbd>: view commit's files
<kbd>space</kbd>: select commit to diff with another commit
</pre>
## Stash
@@ -81,11 +86,20 @@
<kbd>d</kbd>: drop
</pre>
## Commit files
<pre>
<kbd>esc</kbd>: go back
<kbd>c</kbd>: checkout file
<kbd>d</kbd>: discard this commit's changes to this file
<kbd>o</kbd>: open file
</pre>
## Main (Normal)
<pre>
<kbd>PgDn</kbd>: scroll down
<kbd>PgUp</kbd>: scroll up
<kbd>PgDn</kbd>: scroll down (fn+up)
<kbd>PgUp</kbd>: scroll up (fn+down)
</pre>
## Main (Staging)
+128
View File
@@ -0,0 +1,128 @@
# Lazygit menu
## Global
<pre>
<kbd>m</kbd>: bekijk merge/rebase opties
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
<kbd>R</kbd>: verversen
</pre>
## Status
<pre>
<kbd>e</kbd>: verander config file
<kbd>o</kbd>: open config file
<kbd>u</kbd>: check voor updates
<kbd>s</kbd>: wissel naar een recente repo
</pre>
## Bestanden
<pre>
<kbd>c</kbd>: Commit veranderingen
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
<kbd>A</kbd>: wijzig laatste commit
<kbd>C</kbd>: commit veranderingen met de git editor
<kbd>space</kbd>: toggle staged
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
<kbd>e</kbd>: verander bestand
<kbd>o</kbd>: open bestand
<kbd>i</kbd>: voeg toe aan .gitignore
<kbd>r</kbd>: refresh bestanden
<kbd>S</kbd>: stash-bestanden
<kbd>a</kbd>: toggle staged alle
<kbd>t</kbd>: bewerkingen toevoegen
<kbd>D</kbd>: bekijk reset opties
<kbd>enter</kbd>: stage individuele hunks/lijnen
<kbd>f</kbd>: fetch
<kbd>X</kbd>: voor aangepast commando uit
</pre>
## Branches
<pre>
<kbd>space</kbd>: uitchecken
<kbd>o</kbd>: maak een pull-aanvraag
<kbd>c</kbd>: uitchecken bij naam
<kbd>F</kbd>: forceer checkout
<kbd>n</kbd>: nieuwe branch
<kbd>d</kbd>: verwijder branch
<kbd>r</kbd>: rebase branch
<kbd>M</kbd>: merge in met huidige checked out branch
<kbd>f</kbd>: fast-forward this branch from its upstream
</pre>
## Commits
<pre>
<kbd>s</kbd>: squash beneden
<kbd>r</kbd>: hernoem commit
<kbd>R</kbd>: rename commit with editor
<kbd>g</kbd>: reset naar deze commit
<kbd>f</kbd>: Fixup commit
<kbd>F</kbd>: creëer fixup commit voor deze commit
<kbd>S</kbd>: squash bovenstaande commits
<kbd>d</kbd>: verwijder commit
<kbd>ctrl+j</kbd>: verplaats commit 1 omlaag
<kbd>ctrl+k</kbd>: verplaats commit 1 omhoog
<kbd>e</kbd>: verander commit
<kbd>A</kbd>: wijzig commit met staged veranderingen
<kbd>p</kbd>: pick commit (when mid-rebase)
<kbd>t</kbd>: commit omgedaan maken
<kbd>c</kbd>: kopiëer commit (cherry-pick)
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
<kbd>v</kbd>: plak commits (cherry-pick)
<kbd>enter</kbd>: bekijk gecommite bestanden
<kbd>space</kbd>: select commit to diff with another commit
</pre>
## Stash
<pre>
<kbd>space</kbd>: toepassen
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
</pre>
## Commit bestanden
<pre>
<kbd>esc</kbd>: ga terug
<kbd>c</kbd>: bestand uitchecken
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
<kbd>o</kbd>: open bestand
</pre>
## Hoofd (Stage Lines/Hunks)
<pre>
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>▲</kbd>: selecteer de vorige lijn
<kbd>▼</kbd>: selecteer de volgende lijn
<kbd>◄</kbd>: selecteer de vorige hunk
<kbd>►</kbd>: selecteer de volgende hunk
<kbd>space</kbd>: stage lijn
<kbd>a</kbd>: stage hunk
</pre>
## Hoofd (Merging)
<pre>
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick beide hunks
<kbd>◄</kbd>: selecteer voorgaand conflict
<kbd>►</kbd>: selecteer volgende conflict
<kbd>▲</kbd>: selecteer bovenste hunk
<kbd>▼</kbd>: selecteer onderste hunk
<kbd>z</kbd>: ongedaan maken
</pre>
## Hoofd (Normaal)
<pre>
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
</pre>
+128
View File
@@ -0,0 +1,128 @@
# Lazygit menu
## Globalne
<pre>
<kbd>m</kbd>: view merge/rebase options
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
<kbd>R</kbd>: odśwież
</pre>
## Status
<pre>
<kbd>e</kbd>: edytuj plik konfiguracyjny
<kbd>o</kbd>: otwórz plik konfiguracyjny
<kbd>u</kbd>: sprawdź aktualizacje
<kbd>s</kbd>: switch to a recent repo
</pre>
## Pliki
<pre>
<kbd>c</kbd>: commituj zmiany
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
<kbd>C</kbd>: commituj zmiany używając edytora z gita
<kbd>space</kbd>: przełącz zatwierdzenie
<kbd>d</kbd>: view 'discard changes' options
<kbd>e</kbd>: edytuj plik
<kbd>o</kbd>: otwórz plik
<kbd>i</kbd>: dodaj do .gitignore
<kbd>r</kbd>: odśwież pliki
<kbd>S</kbd>: przechowaj pliki
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
<kbd>t</kbd>: dodaj łatkę
<kbd>D</kbd>: view reset options
<kbd>enter</kbd>: zatwierdź pojedyncze linie
<kbd>f</kbd>: fetch
<kbd>X</kbd>: execute custom command
</pre>
## Gałęzie
<pre>
<kbd>space</kbd>: przełącz
<kbd>o</kbd>: utwórz żądanie wyciągnięcia
<kbd>c</kbd>: przełącz używając nazwy
<kbd>F</kbd>: wymuś przełączenie
<kbd>n</kbd>: nowa gałąź
<kbd>d</kbd>: usuń gałąź
<kbd>r</kbd>: rebase branch
<kbd>M</kbd>: scal do obecnej gałęzi
<kbd>f</kbd>: fast-forward this branch from its upstream
</pre>
## Commity
<pre>
<kbd>s</kbd>: ściśnij w dół
<kbd>r</kbd>: przemianuj commit
<kbd>R</kbd>: przemianuj commit w edytorze
<kbd>g</kbd>: zresetuj do tego commita
<kbd>f</kbd>: napraw commit
<kbd>F</kbd>: create fixup commit for this commit
<kbd>S</kbd>: squash above commits
<kbd>d</kbd>: delete commit
<kbd>ctrl+j</kbd>: move commit down one
<kbd>ctrl+k</kbd>: move commit up one
<kbd>e</kbd>: edit commit
<kbd>A</kbd>: amend commit with staged changes
<kbd>p</kbd>: pick commit (when mid-rebase)
<kbd>t</kbd>: revert commit
<kbd>c</kbd>: copy commit (cherry-pick)
<kbd>C</kbd>: copy commit range (cherry-pick)
<kbd>v</kbd>: paste commits (cherry-pick)
<kbd>enter</kbd>: view commit's files
<kbd>space</kbd>: select commit to diff with another commit
</pre>
## Schowek
<pre>
<kbd>space</kbd>: zastosuj
<kbd>g</kbd>: wyciągnij
<kbd>d</kbd>: porzuć
</pre>
## Commit files
<pre>
<kbd>esc</kbd>: go back
<kbd>c</kbd>: checkout file
<kbd>d</kbd>: discard this commit's changes to this file
<kbd>o</kbd>: otwórz plik
</pre>
## Main (Normal)
<pre>
<kbd>PgDn</kbd>: scroll down (fn+up)
<kbd>PgUp</kbd>: scroll up (fn+down)
</pre>
## Main (Zatwierdzanie)
<pre>
<kbd>esc</kbd>: wróć do panelu plików
<kbd>▲</kbd>: select previous line
<kbd>▼</kbd>: select next line
<kbd>◄</kbd>: select previous hunk
<kbd>►</kbd>: select next hunk
<kbd>space</kbd>: zatwierdź linię
<kbd>a</kbd>: zatwierdź kawałek
</pre>
## Main (Merging)
<pre>
<kbd>esc</kbd>: wróć do panelu plików
<kbd>space</kbd>: pick hunk
<kbd>b</kbd>: pick both hunks
<kbd>◄</kbd>: select previous conflict
<kbd>►</kbd>: select next conflict
<kbd>▲</kbd>: select top hunk
<kbd>▼</kbd>: select bottom hunk
<kbd>z</kbd>: undo
</pre>
-66
View File
@@ -1,66 +0,0 @@
module github.com/jesseduffield/lazygit
go 1.12
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-errors/errors v1.0.1
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-20190305102456-f0f0ab442e6c
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb
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/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
)
-123
View File
@@ -1,123 +0,0 @@
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/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/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/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
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/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/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-20190305102456-f0f0ab442e6c h1:ZmCZFQukfddcISnY8R5YYMCYJX0mc1Wfmq/MhEQViUE=
github.com/jesseduffield/gocui v0.0.0-20190305102456-f0f0ab442e6c/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406 h1:iYMH6h6SuWuBkIzRtymosE8NpSgTK0oRMfyTdVWgxzc=
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY=
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
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/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 h1:7ory6RlsEkeK89iyV7Imz3sVz8YHeSw29w3PehpCWC0=
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.6 h1:SooTCzUOOs899x/M7gmSS+dgL+AUfSWqAcHLN3auCic=
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
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/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/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/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/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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.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/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=
+39 -5
View File
@@ -1,6 +1,7 @@
package app
import (
"bufio"
"fmt"
"io"
"io/ioutil"
@@ -18,6 +19,9 @@ import (
"github.com/sirupsen/logrus"
)
// we hit the limit: putting this here for now
const HaveNotHitRollbarLimit = false
// App struct
type App struct {
closers []io.Closer
@@ -35,6 +39,7 @@ type App struct {
func newProductionLogger(config config.AppConfigurer) *logrus.Logger {
log := logrus.New()
log.Out = ioutil.Discard
log.SetLevel(logrus.ErrorLevel)
return log
}
@@ -44,8 +49,18 @@ func globalConfigDir() string {
return configDir.Path
}
func getLogLevel() logrus.Level {
strLevel := os.Getenv("LOG_LEVEL")
level, err := logrus.ParseLevel(strLevel)
if err != nil {
return logrus.DebugLevel
}
return level
}
func newDevelopmentLogger(config config.AppConfigurer) *logrus.Logger {
log := logrus.New()
log.SetLevel(getLogLevel())
file, err := os.OpenFile(filepath.Join(globalConfigDir(), "development.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function)
@@ -68,7 +83,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{}
if config.GetUserConfig().GetString("reporting") == "on" {
if HaveNotHitRollbarLimit && config.GetUserConfig().GetString("reporting") == "on" {
// this isn't really a secret token: it only has permission to push new rollbar items
hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment)
log.Hooks.Add(hook)
@@ -103,12 +118,13 @@ func NewApp(config config.AppConfigurer) (*App, error) {
if err != nil {
return app, err
}
if err := app.setupRepo(); err != nil {
return app, err
}
app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr, app.Config)
if err != nil {
if strings.Contains(err.Error(), "Not a git repository") {
fmt.Println("Not in a git repository. Use `git init` to create a new one")
os.Exit(1)
}
return app, err
}
app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater)
@@ -118,6 +134,24 @@ func NewApp(config config.AppConfigurer) (*App, error) {
return app, nil
}
func (app *App) setupRepo() error {
// if we are not in a git repo, we ask if we want to `git init`
if err := app.OSCommand.RunCommand("git status"); err != nil {
if !strings.Contains(err.Error(), "Not a git repository") {
return err
}
fmt.Print(app.Tr.SLocalize("CreateRepo"))
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if strings.Trim(response, " \n") != "y" {
os.Exit(1)
}
if err := app.OSCommand.RunCommand("git init"); err != nil {
return err
}
}
return nil
}
func (app *App) Run() error {
if app.ClientContext == "INTERACTIVE_REBASE" {
return app.Rebase()
+4 -1
View File
@@ -9,7 +9,7 @@ import (
type Commit struct {
Sha string
Name string
Status string // one of "unpushed", "pushed", "merged", or "rebasing"
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
DisplayString string
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
Copied bool // to know if this commit is ready to be cherry-picked somewhere
@@ -23,6 +23,7 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
blue := color.New(color.FgBlue)
cyan := color.New(color.FgCyan)
white := color.New(color.FgWhite)
magenta := color.New(color.FgMagenta)
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
@@ -39,6 +40,8 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
shaColor = green
case "rebasing":
shaColor = blue
case "selected":
shaColor = magenta
default:
shaColor = white
}
+13
View File
@@ -0,0 +1,13 @@
package commands
// CommitFile : A git commit file
type CommitFile struct {
Sha string
Name string
DisplayString string
}
// GetDisplayStrings is a function.
func (f *CommitFile) GetDisplayStrings(isFocused bool) []string {
return []string{f.DisplayString}
}
+14
View File
@@ -0,0 +1,14 @@
package commands
import "github.com/go-errors/errors"
// WrapError wraps an error for the sake of showing a stack trace at the top level
// the go-errors package, for some reason, does not return nil when you try to wrap
// a non-error, so we're just doing it here
func WrapError(err error) error {
if err == nil {
return err
}
return errors.Wrap(err, 0)
}
+1 -6
View File
@@ -5,14 +5,12 @@ package commands
import (
"bufio"
"bytes"
"os"
"strings"
"unicode/utf8"
"github.com/go-errors/errors"
"github.com/jesseduffield/pty"
"github.com/mgutz/str"
)
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
@@ -20,10 +18,7 @@ import (
// As return of output you need to give a string that will be written to stdin
// NOTE: If the return data is empty it won't written anything to stdin
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
splitCmd := str.ToArgv(command)
cmd := c.command(splitCmd[0], splitCmd[1:]...)
cmd.Env = os.Environ()
cmd := c.ExecutableFromString(command)
cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
var stderr bytes.Buffer
+1
View File
@@ -14,6 +14,7 @@ type File struct {
HasInlineMergeConflicts bool
DisplayString string
Type string // one of 'file', 'directory', and 'other'
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
}
// GetDisplayStrings returns the display string of a file
+246 -37
View File
@@ -25,18 +25,18 @@ func verifyInGitRepo(runCmd func(string) error) error {
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
for {
f, err := stat(".git")
_, err := stat(".git")
if err == nil && f.IsDir() {
if err == nil {
return nil
}
if !os.IsNotExist(err) {
return errors.Wrap(err, 0)
return WrapError(err)
}
if err = chdir(".."); err != nil {
return errors.Wrap(err, 0)
return WrapError(err)
}
}
}
@@ -72,6 +72,7 @@ type GitCommand struct {
getGlobalGitConfig func(string) (string, error)
getLocalGitConfig func(string) (string, error)
removeFile func(string) error
DotGitDir string
}
// NewGitCommand it runs git commands
@@ -99,6 +100,11 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
}
}
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
if err != nil {
return nil, err
}
return &GitCommand{
Log: log,
OSCommand: osCommand,
@@ -109,9 +115,31 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
getGlobalGitConfig: gitconfig.Global,
getLocalGitConfig: gitconfig.Local,
removeFile: os.RemoveAll,
DotGitDir: dotGitDir,
}, nil
}
func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
f, err := stat(".git")
if err != nil {
return "", err
}
if f.IsDir() {
return ".git", nil
}
fileBytes, err := readFile(".git")
if err != nil {
return "", err
}
fileContent := string(fileBytes)
if !strings.HasPrefix(fileContent, "gitdir: ") {
return "", errors.New(".git is a file which suggests we are in a submodule but the file's contents do not contain a gitdir pointing to the actual .git directory")
}
return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
}
// GetStashEntries stash entryies
func (c *GitCommand) GetStashEntries() []*StashEntry {
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
@@ -159,6 +187,7 @@ func (c *GitCommand) GetStatusFiles() []*File {
HasMergeConflicts: change == "UU" || change == "AA" || change == "DU",
HasInlineMergeConflicts: change == "UU" || change == "AA",
Type: c.OSCommand.FileType(filename),
ShortStatus: change,
}
files = append(files, file)
}
@@ -217,11 +246,11 @@ func includesInt(list []int, a int) bool {
// ResetAndClean removes all unstaged changes and removes all untracked files
func (c *GitCommand) ResetAndClean() error {
if err := c.OSCommand.RunCommand("git reset --hard HEAD"); err != nil {
if err := c.ResetHardHead(); err != nil {
return err
}
return c.OSCommand.RunCommand("git clean -fd")
return c.RemoveUntrackedFiles()
}
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@@ -274,8 +303,8 @@ func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCrede
}
// ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string) error {
return c.OSCommand.RunCommand(fmt.Sprintf("git reset %s", sha))
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
return c.OSCommand.RunCommand(fmt.Sprintf("git reset --%s %s", strength, sha))
}
// NewBranch create new branch
@@ -334,8 +363,8 @@ func (c *GitCommand) usingGpg() bool {
}
// Commit commits to git
func (c *GitCommand) Commit(message string) (*exec.Cmd, error) {
command := fmt.Sprintf("git commit -m %s", c.OSCommand.Quote(message))
func (c *GitCommand) Commit(message string, flags string) (*exec.Cmd, error) {
command := fmt.Sprintf("git commit %s -m %s", flags, c.OSCommand.Quote(message))
if c.usingGpg() {
return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil
}
@@ -408,7 +437,7 @@ func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
// GitStatus returns the plaintext short status of the repo
func (c *GitCommand) GitStatus() (string, error) {
return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --short")
return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --porcelain")
}
// IsInMergeState states whether we are still mid-merge
@@ -423,14 +452,14 @@ func (c *GitCommand) IsInMergeState() (bool, error) {
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func (c *GitCommand) RebaseMode() (string, error) {
exists, err := c.OSCommand.FileExists(".git/rebase-apply")
exists, err := c.OSCommand.FileExists(fmt.Sprintf("%s/rebase-apply", c.DotGitDir))
if err != nil {
return "", err
}
if exists {
return "normal", nil
}
exists, err = c.OSCommand.FileExists(".git/rebase-merge")
exists, err = c.OSCommand.FileExists(fmt.Sprintf("%s/rebase-merge", c.DotGitDir))
if exists {
return "interactive", err
} else {
@@ -438,8 +467,8 @@ func (c *GitCommand) RebaseMode() (string, error) {
}
}
// RemoveFile directly
func (c *GitCommand) RemoveFile(file *File) error {
// DiscardAllFileChanges directly
func (c *GitCommand) DiscardAllFileChanges(file *File) error {
// if the file isn't tracked, we assume you want to delete it
quotedFileName := c.OSCommand.Quote(file.Name)
if file.HasStagedChanges {
@@ -450,7 +479,12 @@ func (c *GitCommand) RemoveFile(file *File) error {
if !file.Tracked {
return c.removeFile(file.Name)
}
// if the file is tracked, we assume you want to just check it out
return c.DiscardUnstagedFileChanges(file)
}
// DiscardUnstagedFileChanges directly
func (c *GitCommand) DiscardUnstagedFileChanges(file *File) error {
quotedFileName := c.OSCommand.Quote(file.Name)
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -- %s", quotedFileName))
}
@@ -575,7 +609,7 @@ func (c *GitCommand) ApplyPatch(patch string) (string, error) {
return "", err
}
defer func() { _ = c.OSCommand.RemoveFile(filename) }()
defer func() { _ = c.OSCommand.Remove(filename) }()
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git apply --cached %s", c.OSCommand.Quote(filename)))
}
@@ -588,7 +622,7 @@ func (c *GitCommand) FastForward(branchName string) error {
func (c *GitCommand) RunSkipEditorCommand(command string) error {
cmd := c.OSCommand.ExecutableFromString(command)
cmd.Env = append(
os.Environ(),
cmd.Env,
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
"EDITOR="+c.OSCommand.GetLazygitPath(),
)
@@ -608,12 +642,12 @@ func (c *GitCommand) GenericMerge(commandType string, command string) error {
}
func (c *GitCommand) RewordCommit(commits []*Commit, index int) (*exec.Cmd, error) {
todo, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
if err != nil {
return nil, err
}
return c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, false)
return c.PrepareInteractiveRebaseCommand(sha, todo, false)
}
func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error {
@@ -638,12 +672,12 @@ func (c *GitCommand) MoveCommitDown(commits []*Commit, index int) error {
}
func (c *GitCommand) InteractiveRebase(commits []*Commit, index int, action string) error {
todo, err := c.GenerateGenericRebaseTodo(commits, index, action)
todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, action)
if err != nil {
return err
}
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+1].Sha, todo, true)
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
@@ -697,38 +731,45 @@ func (c *GitCommand) SoftReset(baseSha string) error {
return c.OSCommand.RunCommand("git reset --soft " + baseSha)
}
func (c *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, index int, action string) (string, error) {
if len(commits) <= index+1 {
// assuming they aren't picking the bottom commit
return "", errors.New(c.Tr.SLocalize("CannotRebaseOntoFirstCommit"))
func (c *GitCommand) GenerateGenericRebaseTodo(commits []*Commit, actionIndex int, action string) (string, string, error) {
baseIndex := actionIndex + 1
if len(commits) <= baseIndex {
return "", "", errors.New(c.Tr.SLocalize("CannotRebaseOntoFirstCommit"))
}
if action == "squash" || action == "fixup" {
baseIndex++
if len(commits) <= baseIndex {
return "", "", errors.New(c.Tr.SLocalize("CannotSquashOntoSecondCommit"))
}
}
todo := ""
for i, commit := range commits[0 : index+1] {
for i, commit := range commits[0:baseIndex] {
a := "pick"
if i == index {
if i == actionIndex {
a = action
}
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
}
return todo, nil
return todo, commits[baseIndex].Sha, nil
}
// AmendTo amends the given commit with whatever files are staged
func (c *GitCommand) AmendTo(sha string) error {
if err := c.OSCommand.RunCommand(fmt.Sprintf("git commit --fixup=%s", sha)); err != nil {
if err := c.CreateFixupCommit(sha); err != nil {
return err
}
return c.RunSkipEditorCommand(
fmt.Sprintf(
"git rebase --interactive --autostash --autosquash %s^", sha,
),
)
return c.SquashAllAboveFixupCommits(sha)
}
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
func (c *GitCommand) EditRebaseTodo(index int, action string) error {
fileName := ".git/rebase-merge/git-rebase-todo"
fileName := fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.DotGitDir)
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return err
@@ -760,7 +801,7 @@ func (c *GitCommand) getTodoCommitCount(content []string) int {
// MoveTodoDown moves a rebase todo item down by one position
func (c *GitCommand) MoveTodoDown(index int) error {
fileName := ".git/rebase-merge/git-rebase-todo"
fileName := fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.DotGitDir)
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return err
@@ -796,3 +837,171 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
return c.OSCommand.RunPreparedCommand(cmd)
}
// GetCommitFiles get the specified commit files
func (c *GitCommand) GetCommitFiles(commitSha string) ([]*CommitFile, error) {
cmd := fmt.Sprintf("git show --pretty= --name-only %s", commitSha)
files, err := c.OSCommand.RunCommandWithOutput(cmd)
if err != nil {
return nil, err
}
commitFiles := make([]*CommitFile, 0)
for _, file := range strings.Split(strings.TrimRight(files, "\n"), "\n") {
commitFiles = append(commitFiles, &CommitFile{
Sha: commitSha,
Name: file,
DisplayString: file,
})
}
return commitFiles, nil
}
// ShowCommitFile get the diff of specified commit file
func (c *GitCommand) ShowCommitFile(commitSha, fileName string) (string, error) {
cmd := fmt.Sprintf("git show --color %s -- %s", commitSha, fileName)
return c.OSCommand.RunCommandWithOutput(cmd)
}
// CheckoutFile checks out the file for the given commit
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
cmd := fmt.Sprintf("git checkout %s %s", commitSha, fileName)
return c.OSCommand.RunCommand(cmd)
}
// DiscardOldFileChanges discards changes to a file from an old commit
func (c *GitCommand) DiscardOldFileChanges(commits []*Commit, commitIndex int, fileName string) error {
if len(commits)-1 < commitIndex {
return errors.New("index outside of range of commits")
}
// we can make this GPG thing possible it just means we need to do this in two parts:
// one where we handle the possibility of a credential request, and the other
// where we continue the rebase
if c.usingGpg() {
return errors.New(c.Tr.SLocalize("DisabledForGPG"))
}
todo, sha, err := c.GenerateGenericRebaseTodo(commits, commitIndex, "edit")
if err != nil {
return err
}
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
if err := c.OSCommand.RunPreparedCommand(cmd); err != nil {
return err
}
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
if err := c.OSCommand.RunCommand(fmt.Sprintf("git cat-file -e HEAD^:%s", fileName)); err != nil {
if err := c.OSCommand.Remove(fileName); err != nil {
return err
}
if err := c.StageFile(fileName); err != nil {
return err
}
} else {
if err := c.CheckoutFile("HEAD^", fileName); err != nil {
return err
}
}
// amend the commit
cmd, err = c.AmendHead()
if cmd != nil {
return errors.New("received unexpected pointer to cmd")
}
if err != nil {
return err
}
// continue
return c.GenericMerge("rebase", "continue")
}
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
return c.OSCommand.RunCommand("git checkout -- .")
}
// RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error {
return c.OSCommand.RunCommand("git clean -fd")
}
// ResetHardHead runs `git reset --hard HEAD`
func (c *GitCommand) ResetHardHead() error {
return c.OSCommand.RunCommand("git reset --hard HEAD")
}
// ResetSoftHead runs `git reset --soft HEAD`
func (c *GitCommand) ResetSoftHead() error {
return c.OSCommand.RunCommand("git reset --soft HEAD")
}
// DiffCommits show diff between commits
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
cmd := fmt.Sprintf("git diff --color %s %s", sha1, sha2)
return c.OSCommand.RunCommandWithOutput(cmd)
}
// CreateFixupCommit creates a commit that fixes up a previous commit
func (c *GitCommand) CreateFixupCommit(sha string) error {
cmd := fmt.Sprintf("git commit --fixup=%s", sha)
return c.OSCommand.RunCommand(cmd)
}
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
func (c *GitCommand) SquashAllAboveFixupCommits(sha string) error {
return c.RunSkipEditorCommand(
fmt.Sprintf(
"git rebase --interactive --autostash --autosquash %s^",
sha,
),
)
}
// StashSaveStagedChanges stashes only the currently staged changes. This takes a few steps
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (c *GitCommand) StashSaveStagedChanges(message string) error {
if err := c.OSCommand.RunCommand("git stash --keep-index"); err != nil {
return err
}
if err := c.StashSave(message); err != nil {
return err
}
if err := c.OSCommand.RunCommand("git stash apply stash@{1}"); err != nil {
return err
}
if err := c.OSCommand.PipeCommands("git stash show -p", "git apply -R"); err != nil {
return err
}
if err := c.OSCommand.RunCommand("git stash drop stash@{1}"); err != nil {
return err
}
// if you had staged an untracked file, that will now appear as 'AD' in git status
// meaning it's deleted in your working tree but added in your index. Given that it's
// now safely stashed, we need to remove it.
files := c.GetStatusFiles()
for _, file := range files {
if file.ShortStatus == "AD" {
if err := c.UnStageFile(file.Name, false); err != nil {
return err
}
}
}
return nil
}
+500 -9
View File
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/test"
"github.com/stretchr/testify/assert"
@@ -371,6 +372,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "MM file1.txt",
Type: "other",
ShortStatus: "MM",
},
{
Name: "file3.txt",
@@ -381,6 +383,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "A file3.txt",
Type: "other",
ShortStatus: "A ",
},
{
Name: "file2.txt",
@@ -391,6 +394,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "AM file2.txt",
Type: "other",
ShortStatus: "AM",
},
{
Name: "file4.txt",
@@ -401,6 +405,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
HasMergeConflicts: false,
DisplayString: "?? file4.txt",
Type: "other",
ShortStatus: "??",
},
}
@@ -616,12 +621,12 @@ 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)
assert.EqualValues(t, []string{"reset", "--hard", "78976bc"}, args)
return exec.Command("echo")
}
assert.NoError(t, gitCmd.ResetToCommit("78976bc"))
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard"))
}
// TestGitCommandNewBranch is a function.
@@ -801,6 +806,7 @@ func TestGitCommandCommit(t *testing.T) {
command func(string, ...string) *exec.Cmd
getGlobalGitConfig func(string) (string, error)
test func(*exec.Cmd, error)
flags string
}
scenarios := []scenario{
@@ -808,7 +814,7 @@ func TestGitCommandCommit(t *testing.T) {
"Commit using gpg",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "bash", cmd)
assert.EqualValues(t, []string{"-c", `git commit -m 'test'`}, args)
assert.EqualValues(t, []string{"-c", `git commit -m 'test'`}, args)
return exec.Command("echo")
},
@@ -819,6 +825,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.NotNil(t, cmd)
assert.Nil(t, err)
},
"",
},
{
"Commit without using gpg",
@@ -835,6 +842,24 @@ func TestGitCommandCommit(t *testing.T) {
assert.Nil(t, cmd)
assert.Nil(t, err)
},
"",
},
{
"Commit with --no-verify flag",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--no-verify", "-m", "test"}, args)
return exec.Command("echo")
},
func(string) (string, error) {
return "false", nil
},
func(cmd *exec.Cmd, err error) {
assert.Nil(t, cmd)
assert.Nil(t, err)
},
"--no-verify",
},
{
"Commit without using gpg with an error",
@@ -851,6 +876,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.Nil(t, cmd)
assert.Error(t, err)
},
"",
},
}
@@ -859,7 +885,7 @@ func TestGitCommandCommit(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
gitCmd.OSCommand.command = s.command
s.test(gitCmd.Commit("test"))
s.test(gitCmd.Commit("test", s.flags))
})
}
}
@@ -1140,8 +1166,8 @@ func TestGitCommandIsInMergeState(t *testing.T) {
}
}
// TestGitCommandRemoveFile is a function.
func TestGitCommandRemoveFile(t *testing.T) {
// TestGitCommandDiscardAllFileChanges is a function.
func TestGitCommandDiscardAllFileChanges(t *testing.T) {
type scenario struct {
testName string
command func() (func(string, ...string) *exec.Cmd, *[][]string)
@@ -1337,7 +1363,7 @@ func TestGitCommandRemoveFile(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.OSCommand.command, cmdsCalled = s.command()
gitCmd.removeFile = s.removeFile
s.test(cmdsCalled, gitCmd.RemoveFile(s.file))
s.test(cmdsCalled, gitCmd.DiscardAllFileChanges(s.file))
})
}
}
@@ -1707,7 +1733,472 @@ func TestGitCommandRebaseBranch(t *testing.T) {
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.RebaseBranch(s.arg))
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.RebaseBranch(s.arg))
})
}
}
// TestGitCommandCheckoutFile is a function.
func TestGitCommandCheckoutFile(t *testing.T) {
type scenario struct {
testName string
commitSha string
fileName string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"typical case",
"11af912",
"test999.txt",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git checkout 11af912 test999.txt",
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
{
"returns error if there is one",
"11af912",
"test999.txt",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git checkout 11af912 test999.txt",
Replace: "test",
},
}),
func(err error) {
assert.Error(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.CheckoutFile(s.commitSha, s.fileName))
})
}
}
// TestGitCommandDiscardOldFileChanges is a function.
func TestGitCommandDiscardOldFileChanges(t *testing.T) {
type scenario struct {
testName string
getLocalGitConfig func(string) (string, error)
commits []*Commit
commitIndex int
fileName string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"returns error when index outside of range of commits",
func(string) (string, error) {
return "", nil
},
[]*Commit{},
0,
"test999.txt",
nil,
func(err error) {
assert.Error(t, err)
},
},
{
"returns error when using gpg",
func(string) (string, error) {
return "true", nil
},
[]*Commit{{Name: "commit", Sha: "123456"}},
0,
"test999.txt",
nil,
func(err error) {
assert.Error(t, err)
},
},
{
"checks out file if it already existed",
func(string) (string, error) {
return "", nil
},
[]*Commit{
{Name: "commit", Sha: "123456"},
{Name: "commit2", Sha: "abcdef"},
},
0,
"test999.txt",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git rebase --interactive --autostash abcdef",
Replace: "echo",
},
{
Expect: "git cat-file -e HEAD^:test999.txt",
Replace: "echo",
},
{
Expect: "git checkout HEAD^ test999.txt",
Replace: "echo",
},
{
Expect: "git commit --amend --no-edit",
Replace: "echo",
},
{
Expect: "git rebase --continue",
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
// test for when the file was created within the commit requires a refactor to support proper mocks
// currently we'd need to mock out the os.Remove function and that's gonna introduce tech debt
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
gitCmd.getLocalGitConfig = s.getLocalGitConfig
s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
})
}
}
// TestGitCommandShowCommitFile is a function.
func TestGitCommandShowCommitFile(t *testing.T) {
type scenario struct {
testName string
commitSha string
fileName string
command func(string, ...string) *exec.Cmd
test func(string, error)
}
scenarios := []scenario{
{
"valid case",
"123456",
"hello.txt",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git show --color 123456 -- hello.txt",
Replace: "echo -n hello",
},
}),
func(str string, err error) {
assert.NoError(t, err)
assert.Equal(t, "hello", str)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.ShowCommitFile(s.commitSha, s.fileName))
})
}
}
// TestGitCommandGetCommitFiles is a function.
func TestGitCommandGetCommitFiles(t *testing.T) {
type scenario struct {
testName string
commitSha string
command func(string, ...string) *exec.Cmd
test func([]*CommitFile, error)
}
scenarios := []scenario{
{
"valid case",
"123456",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: "git show --pretty= --name-only 123456",
Replace: "echo 'hello\nworld'",
},
}),
func(commitFiles []*CommitFile, err error) {
assert.NoError(t, err)
assert.Equal(t, []*CommitFile{
{Sha: "123456", Name: "hello", DisplayString: "hello"},
{Sha: "123456", Name: "world", DisplayString: "world"},
}, commitFiles)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.GetCommitFiles(s.commitSha))
})
}
}
// TestGitCommandDiscardUnstagedFileChanges is a function.
func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
file *File
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"valid case",
&File{Name: "test.txt"},
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: `git checkout -- "test.txt"`,
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.DiscardUnstagedFileChanges(s.file))
})
}
}
// TestGitCommandDiscardAnyUnstagedFileChanges is a function.
func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"valid case",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: `git checkout -- .`,
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.DiscardAnyUnstagedFileChanges())
})
}
}
// TestGitCommandRemoveUntrackedFiles is a function.
func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"valid case",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: `git clean -fd`,
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.RemoveUntrackedFiles())
})
}
}
// TestGitCommandResetHardHead is a function.
func TestGitCommandResetHardHead(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"valid case",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: `git reset --hard HEAD`,
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.ResetHardHead())
})
}
}
// TestGitCommandCreateFixupCommit is a function.
func TestGitCommandCreateFixupCommit(t *testing.T) {
type scenario struct {
testName string
sha string
command func(string, ...string) *exec.Cmd
test func(error)
}
scenarios := []scenario{
{
"valid case",
"12345",
test.CreateMockCommand(t, []*test.CommandSwapper{
{
Expect: `git commit --fixup=12345`,
Replace: "echo",
},
}),
func(err error) {
assert.NoError(t, err)
},
},
}
gitCmd := NewDummyGitCommand()
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd.OSCommand.command = s.command
s.test(gitCmd.CreateFixupCommit(s.sha))
})
}
}
func TestFindDotGitDir(t *testing.T) {
type scenario struct {
testName string
stat func(string) (os.FileInfo, error)
readFile func(filename string) ([]byte, error)
test func(string, error)
}
scenarios := []scenario{
{
".git is a directory",
func(dotGit string) (os.FileInfo, error) {
assert.Equal(t, ".git", dotGit)
return os.Stat("testdata/a_dir")
},
func(dotGit string) ([]byte, error) {
assert.Fail(t, "readFile should not be called if .git is a directory")
return nil, nil
},
func(gitDir string, err error) {
assert.NoError(t, err)
assert.Equal(t, ".git", gitDir)
},
},
{
".git is a file",
func(dotGit string) (os.FileInfo, error) {
assert.Equal(t, ".git", dotGit)
return os.Stat("testdata/a_file")
},
func(dotGit string) ([]byte, error) {
assert.Equal(t, ".git", dotGit)
return []byte("gitdir: blah\n"), nil
},
func(gitDir string, err error) {
assert.NoError(t, err)
assert.Equal(t, "blah", gitDir)
},
},
{
"os.Stat returns an error",
func(dotGit string) (os.FileInfo, error) {
assert.Equal(t, ".git", dotGit)
return nil, errors.New("error")
},
func(dotGit string) ([]byte, error) {
assert.Fail(t, "readFile should not be called os.Stat returns an error")
return nil, nil
},
func(gitDir string, err error) {
assert.Error(t, err)
},
},
{
"readFile returns an error",
func(dotGit string) (os.FileInfo, error) {
assert.Equal(t, ".git", dotGit)
return os.Stat("testdata/a_file")
},
func(dotGit string) ([]byte, error) {
return nil, errors.New("error")
},
func(gitDir string, err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
s.test(findDotGitDir(s.stat, s.readFile))
})
}
}
+85 -13
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/go-errors/errors"
@@ -77,8 +78,9 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
// ExecutableFromString takes a string like `git status` and returns an executable command for it
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr)
c.Log.Info(splitCmd)
return c.command(splitCmd[0], splitCmd[1:]...)
cmd := c.command(splitCmd[0], splitCmd[1:]...)
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
return cmd
}
// RunCommandWithOutputLive runs RunCommandWithOutputLiveWrapper
@@ -145,7 +147,7 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
// errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output
if outputString == "" {
return "", errors.Wrap(err, 0)
return "", WrapError(err)
}
return outputString, errors.New(outputString)
}
@@ -200,8 +202,13 @@ func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
}
// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it
// TODO: see if this needs to exist, given that ExecutableFromString does the same things
func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd {
return c.command(cmdName, commandArgs...)
cmd := c.command(cmdName, commandArgs...)
if cmd != nil {
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
}
return cmd
}
// Quote wraps a message in platform-specific quotation marks
@@ -224,13 +231,13 @@ func (c *OSCommand) Unquote(message string) string {
func (c *OSCommand) AppendLineToFile(filename, line string) error {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return errors.Wrap(err, 0)
return WrapError(err)
}
defer f.Close()
_, err = f.WriteString("\n" + line)
if err != nil {
return errors.Wrap(err, 0)
return WrapError(err)
}
return nil
}
@@ -240,25 +247,25 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
tmpfile, err := ioutil.TempFile("", filename)
if err != nil {
c.Log.Error(err)
return "", errors.Wrap(err, 0)
return "", WrapError(err)
}
if _, err := tmpfile.WriteString(content); err != nil {
c.Log.Error(err)
return "", errors.Wrap(err, 0)
return "", WrapError(err)
}
if err := tmpfile.Close(); err != nil {
c.Log.Error(err)
return "", errors.Wrap(err, 0)
return "", WrapError(err)
}
return tmpfile.Name(), nil
}
// RemoveFile removes a file at the specified path
func (c *OSCommand) RemoveFile(filename string) error {
err := os.Remove(filename)
return errors.Wrap(err, 0)
// Remove removes a file or directory at the specified path
func (c *OSCommand) Remove(filename string) error {
err := os.RemoveAll(filename)
return WrapError(err)
}
// FileExists checks whether a file exists at the specified path
@@ -296,3 +303,68 @@ func (c *OSCommand) GetLazygitPath() string {
}
return filepath.ToSlash(ex)
}
// RunCustomCommand returns the pointer to a custom command
func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd {
return c.PrepareSubProcess(c.Platform.shell, c.Platform.shellArg, command)
}
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
func (c *OSCommand) PipeCommands(commandStrings ...string) error {
cmds := make([]*exec.Cmd, len(commandStrings))
for i, str := range commandStrings {
cmds[i] = c.ExecutableFromString(str)
}
for i := 0; i < len(cmds)-1; i++ {
stdout, err := cmds[i].StdoutPipe()
if err != nil {
return err
}
cmds[i+1].Stdin = stdout
}
// keeping this here in case I adapt this code for some other purpose in the future
// cmds[len(cmds)-1].Stdout = os.Stdout
finalErrors := []string{}
wg := sync.WaitGroup{}
wg.Add(len(cmds))
for _, cmd := range cmds {
currentCmd := cmd
go func() {
stderr, err := currentCmd.StderrPipe()
if err != nil {
c.Log.Error(err)
}
if err := currentCmd.Start(); err != nil {
c.Log.Error(err)
}
if b, err := ioutil.ReadAll(stderr); err == nil {
if len(b) > 0 {
finalErrors = append(finalErrors, string(b))
}
}
if err := currentCmd.Wait(); err != nil {
c.Log.Error(err)
}
wg.Done()
}()
}
wg.Wait()
if len(finalErrors) > 0 {
return errors.New(strings.Join(finalErrors, "\n"))
}
return nil
}
+1 -1
View File
@@ -34,7 +34,7 @@ func getServices() []*Service {
},
{
Name: "bitbucket.org",
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?t=%s",
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
},
{
Name: "gitlab.com",
+2 -2
View File
@@ -64,7 +64,7 @@ func TestCreatePullRequest(t *testing.T) {
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?t=feature/profile-page"})
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"})
return exec.Command("echo")
},
func(err error) {
@@ -83,7 +83,7 @@ func TestCreatePullRequest(t *testing.T) {
}
assert.Equal(t, cmd, "open")
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?t=feature/events"})
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"})
return exec.Command("echo")
},
func(err error) {
View File
View File
+1
View File
@@ -248,6 +248,7 @@ func GetDefaultConfig() []byte {
git:
merging:
manualCommit: false
skipHookPrefix: 'WIP'
update:
method: prompt # can be: prompt | background | never
days: 14 # how often a update is checked for
+14 -4
View File
@@ -31,16 +31,18 @@ type CommitListBuilder struct {
OSCommand *commands.OSCommand
Tr *i18n.Localizer
CherryPickedCommits []*commands.Commit
DiffEntries []*commands.Commit
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, osCommand *commands.OSCommand, tr *i18n.Localizer, cherryPickedCommits []*commands.Commit) (*CommitListBuilder, error) {
func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, osCommand *commands.OSCommand, tr *i18n.Localizer, cherryPickedCommits []*commands.Commit, diffEntries []*commands.Commit) (*CommitListBuilder, error) {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
CherryPickedCommits: cherryPickedCommits,
DiffEntries: diffEntries,
}, nil
}
@@ -96,6 +98,14 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
return nil, err
}
for _, commit := range commits {
for _, entry := range c.DiffEntries {
if entry.Sha == commit.Sha {
commit.Status = "selected"
}
}
}
return commits, nil
}
@@ -113,7 +123,7 @@ func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*commands.C
func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, error) {
rewrittenCount := 0
bytesContent, err := ioutil.ReadFile(".git/rebase-apply/rewritten")
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-apply/rewritten", c.GitCommand.DotGitDir))
if err == nil {
content := string(bytesContent)
rewrittenCount = len(strings.Split(content, "\n"))
@@ -121,7 +131,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
// we know we're rebasing, so lets get all the files whose names have numbers
commits := []*commands.Commit{}
err = filepath.Walk(".git/rebase-apply", func(path string, f os.FileInfo, err error) error {
err = filepath.Walk(fmt.Sprintf("%s/rebase-apply", c.GitCommand.DotGitDir), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 {
rewrittenCount--
return nil
@@ -165,7 +175,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*commands.Commit, erro
// and extracts out the sha and names of commits that we still have to go
// in the rebase:
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*commands.Commit, error) {
bytesContent, err := ioutil.ReadFile(".git/rebase-merge/git-rebase-todo")
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.GitCommand.DotGitDir))
if err != nil {
c.Log.Info(fmt.Sprintf("error occured reading git-rebase-todo: %s", err.Error()))
// we assume an error means the file doesn't exist so we just return
+38 -14
View File
@@ -34,7 +34,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
}
branch := gui.getSelectedBranch()
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil {
return err
}
go func() {
@@ -121,15 +121,7 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
}
branch := gui.getSelectedBranch()
if err := gui.GitCommand.Checkout(branch.Name, false); err != nil {
if err := gui.createErrorPanel(g, err.Error()); err != nil {
return err
}
} else {
gui.State.Panels.Branches.SelectedLine = 0
}
return gui.refreshSidePanels(g)
return gui.handleCheckoutBranch(branch.Name)
}
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
@@ -166,12 +158,44 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
}, nil)
}
func (gui *Gui) handleCheckoutBranch(branchName string) error {
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
if !strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
if err := gui.createErrorPanel(gui.g, err.Error()); err != nil {
return err
}
}
// offer to autostash changes
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), gui.Tr.SLocalize("AutoStashTitle"), gui.Tr.SLocalize("AutoStashPrompt"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + branchName); err != nil {
return gui.createErrorPanel(g, err.Error())
}
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
return gui.createErrorPanel(g, err.Error())
}
// checkout successful so we select the new branch
gui.State.Panels.Branches.SelectedLine = 0
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
if err := gui.refreshSidePanels(g); err != nil {
return err
}
return gui.createErrorPanel(g, err.Error())
}
return gui.refreshSidePanels(g)
}, nil)
}
gui.State.Panels.Branches.SelectedLine = 0
return gui.refreshSidePanels(gui.g)
}
func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.Checkout(gui.trimmedContent(v), false); err != nil {
return gui.createErrorPanel(g, err.Error())
}
return gui.refreshSidePanels(g)
return gui.handleCheckoutBranch(gui.trimmedContent(v))
})
return nil
}
+106
View File
@@ -0,0 +1,106 @@
package gui
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
)
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
selectedLine := gui.State.Panels.CommitFiles.SelectedLine
if selectedLine == -1 {
return nil
}
return gui.State.CommitFiles[selectedLine]
}
func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
commitFile := gui.getSelectedCommitFile(g)
if commitFile == nil {
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
}
if err := gui.focusPoint(0, gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles), v); err != nil {
return err
}
commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name)
if err != nil {
return err
}
return gui.renderString(g, "main", commitText)
}
func (gui *Gui) handleCommitFilesNextLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.CommitFiles
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), false)
return gui.handleCommitFileSelect(gui.g, v)
}
func (gui *Gui) handleCommitFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
panelState := gui.State.Panels.CommitFiles
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), true)
return gui.handleCommitFileSelect(gui.g, v)
}
func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error {
commitsView, err := g.View("commits")
if err != nil {
return err
}
return gui.switchFocus(g, v, commitsView)
}
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
file := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine]
if err := gui.GitCommand.CheckoutFile(file.Sha, file.Name); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.refreshFiles()
}
func (gui *Gui) handleDiscardOldFileChange(g *gocui.Gui, v *gocui.View) error {
fileName := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("DiscardFileChangesTitle"), gui.Tr.SLocalize("DiscardFileChangesPrompt"), func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
if err := gui.GitCommand.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, fileName); err != nil {
if err := gui.handleGenericMergeCommandResult(err); err != nil {
return err
}
}
return gui.refreshSidePanels(gui.g)
})
}, nil)
}
func (gui *Gui) refreshCommitFilesView() error {
commit := gui.getSelectedCommit(gui.g)
if commit == nil {
return nil
}
files, err := gui.GitCommand.GetCommitFiles(commit.Sha)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.State.CommitFiles = files
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil {
return err
}
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
}
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
file := gui.getSelectedCommitFile(g)
return gui.openFile(file.Name)
}
+15 -32
View File
@@ -11,17 +11,18 @@ import (
// runSyncOrAsyncCommand takes the output of a command that may have returned
// either no error, an error, or a subprocess to execute, and if a subprocess
// needs to be set on the gui object, it does so, and then returns the error
func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) error {
// the bool returned tells us whether the calling code should continue
func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) {
if err != nil {
if err != gui.Errors.ErrSubProcess {
return gui.createErrorPanel(gui.g, err.Error())
return false, gui.createErrorPanel(gui.g, err.Error())
}
}
if sub != nil {
gui.SubProcess = sub
return gui.Errors.ErrSubProcess
return false, gui.Errors.ErrSubProcess
}
return nil
return true, nil
}
func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
@@ -29,9 +30,18 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
if message == "" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr"))
}
if err := gui.runSyncOrAsyncCommand(gui.GitCommand.Commit(message)); err != nil {
flags := ""
skipHookPrefix := gui.Config.GetUserConfig().GetString("git.skipHookPrefix")
if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) {
flags = "--no-verify"
}
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.Commit(message, flags))
if err != nil {
return err
}
if !ok {
return nil
}
v.Clear()
_ = v.SetCursor(0, 0)
@@ -61,33 +71,6 @@ 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) + " "
}
+170 -4
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"github.com/fatih/color"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
@@ -36,9 +37,15 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
}
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits), v); err != nil {
return err
}
// if specific diff mode is on, don't show diff
if gui.State.Panels.Commits.SpecificDiffMode {
return nil
}
commitText, err := gui.GitCommand.Show(commit.Sha)
if err != nil {
return err
@@ -48,7 +55,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
g.Update(func(*gocui.Gui) error {
builder, err := git.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits)
builder, err := git.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries)
if err != nil {
return err
}
@@ -71,9 +78,12 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
fmt.Fprint(v, list)
gui.refreshStatus(g)
if v == g.CurrentView() {
if g.CurrentView() == v {
gui.handleCommitSelect(g, v)
}
if g.CurrentView() == gui.getCommitFilesView() {
return gui.refreshCommitFilesView()
}
return nil
})
return nil
@@ -115,7 +125,8 @@ func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error
if commit == nil {
panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
}
if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil {
if err := gui.GitCommand.ResetToCommit(commit.Sha, "mixed"); err != nil {
return gui.createErrorPanel(g, err.Error())
}
if err := gui.refreshCommits(g); err != nil {
@@ -437,3 +448,158 @@ func (gui *Gui) HandlePasteCommits(g *gocui.Gui, v *gocui.View) error {
})
}, nil)
}
func (gui *Gui) handleSwitchToCommitFilesPanel(g *gocui.Gui, v *gocui.View) error {
if err := gui.refreshCommitFilesView(); err != nil {
return err
}
return gui.switchFocus(g, v, gui.getCommitFilesView())
}
func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
selectLimit := 2
// get selected commit
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
}
// if already selected commit delete
if idx, has := gui.hasCommit(gui.State.DiffEntries, commit.Sha); has {
gui.State.DiffEntries = gui.unchooseCommit(gui.State.DiffEntries, idx)
} else {
if len(gui.State.DiffEntries) == selectLimit {
gui.State.DiffEntries = gui.unchooseCommit(gui.State.DiffEntries, 0)
}
gui.State.DiffEntries = append(gui.State.DiffEntries, commit)
}
gui.setDiffMode()
// if selected two commits, display diff between
if len(gui.State.DiffEntries) == selectLimit {
commitText, err := gui.GitCommand.DiffCommits(gui.State.DiffEntries[0].Sha, gui.State.DiffEntries[1].Sha)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.renderString(g, "main", commitText)
}
return nil
}
func (gui *Gui) setDiffMode() {
v := gui.getCommitsView()
if len(gui.State.DiffEntries) != 0 {
gui.State.Panels.Commits.SpecificDiffMode = true
v.Title = gui.Tr.SLocalize("CommitsDiffTitle")
} else {
gui.State.Panels.Commits.SpecificDiffMode = false
v.Title = gui.Tr.SLocalize("CommitsTitle")
}
gui.refreshCommits(gui.g)
}
func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) {
for idx, commit := range commits {
if commit.Sha == target {
return idx, true
}
}
return -1, false
}
func (gui *Gui) unchooseCommit(commits []*commands.Commit, i int) []*commands.Commit {
return append(commits[:i], commits[i+1:]...)
}
func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return nil
}
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("CreateFixupCommit"), gui.Tr.TemplateLocalize(
"SureCreateFixupCommit",
Teml{
"commit": commit.Sha,
},
), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.CreateFixupCommit(commit.Sha); err != nil {
return gui.createErrorPanel(g, err.Error())
}
return gui.refreshSidePanels(gui.g)
}, nil)
}
func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return nil
}
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("SquashAboveCommits"), gui.Tr.TemplateLocalize(
"SureSquashAboveCommits",
Teml{
"commit": commit.Sha,
},
), func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.SLocalize("SquashingStatus"), func() error {
err := gui.GitCommand.SquashAllAboveFixupCommits(commit.Sha)
return gui.handleGenericMergeCommandResult(err)
})
}, nil)
}
type resetOption struct {
description string
command string
}
// GetDisplayStrings is a function.
func (r *resetOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
}
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
commit := gui.getSelectedCommit(g)
if commit == nil {
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
}
strengths := []string{"soft", "mixed", "hard"}
options := make([]*resetOption, len(strengths))
for i, strength := range strengths {
options[i] = &resetOption{
description: fmt.Sprintf("%s reset", strength),
command: fmt.Sprintf("reset --%s %s", strength, commit.Sha),
}
}
handleMenuPress := func(index int) error {
if err := gui.GitCommand.ResetToCommit(commit.Sha, strengths[index]); err != nil {
return err
}
if err := gui.refreshCommits(g); err != nil {
return err
}
if err := gui.refreshFiles(); err != nil {
return err
}
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
return err
}
gui.State.Panels.Commits.SelectedLine = 0
return gui.handleCommitSelect(g, gui.getCommitsView())
}
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
}
+5 -1
View File
@@ -21,7 +21,11 @@ func (gui *Gui) contextTitleMap() map[string]map[string]string {
}
func (gui *Gui) setMainTitle() error {
currentViewName := gui.g.CurrentView().Name()
currentView := gui.g.CurrentView()
if currentView == nil {
return nil
}
currentViewName := currentView.Name()
var newTitle string
if context, ok := gui.State.Contexts[currentViewName]; ok {
newTitle = gui.contextTitleMap()[currentViewName][context]
+205 -51
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"strings"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -63,7 +64,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bo
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
}
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), v); err != nil {
return err
}
@@ -85,6 +86,10 @@ func (gui *Gui) refreshFiles() error {
selectedFile, _ := gui.getSelectedFile(gui.g)
filesView := gui.getFilesView()
if filesView == nil {
// if the filesView hasn't been instantiated yet we just return
return nil
}
if err := gui.refreshStateFiles(); err != nil {
return err
}
@@ -259,35 +264,6 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess
}
func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err == gui.Errors.ErrNoFiles {
return nil
}
return err
}
var deleteVerb string
if file.Tracked {
deleteVerb = gui.Tr.SLocalize("checkout")
} else {
deleteVerb = gui.Tr.SLocalize("delete")
}
message := gui.Tr.TemplateLocalize(
"SureTo",
Teml{
"deleteVerb": deleteVerb,
"fileName": file.Name,
},
)
return gui.createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", message, func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.RemoveFile(file); err != nil {
return err
}
return gui.refreshFiles()
}, nil)
}
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
@@ -302,6 +278,22 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles()
}
func (gui *Gui) handleWIPCommitPress(g *gocui.Gui, filesView *gocui.View) error {
skipHookPreifx := gui.Config.GetUserConfig().GetString("git.skipHookPrefix")
if skipHookPreifx == "" {
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
}
if err := gui.renderString(g, "commitMessage", skipHookPreifx); err != nil {
return err
}
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
return err
}
return gui.handleCommitPress(g, filesView)
}
func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
@@ -328,9 +320,13 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro
question := gui.Tr.SLocalize("SureToAmend")
return gui.createConfirmationPanel(g, filesView, title, question, func(g *gocui.Gui, v *gocui.View) error {
if err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead()); err != nil {
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
if err != nil {
return err
}
if !ok {
return nil
}
return gui.refreshSidePanels(g)
}, nil)
@@ -355,13 +351,14 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) {
}
func (gui *Gui) editFile(filename string) error {
return gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
_, err := gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
return err
}
func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.editFile(file.Name)
@@ -370,7 +367,7 @@ func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.openFile(file.Name)
}
@@ -454,7 +451,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
return gui.createErrorPanel(gui.g, err.Error())
}
return nil
}
@@ -479,15 +476,6 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
return gui.refreshFiles()
}
func (gui *Gui) handleResetAndClean(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("ClearFilePanel"), gui.Tr.SLocalize("SureResetHardHead"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.ResetAndClean(); err != nil {
gui.createErrorPanel(g, err.Error())
}
return gui.refreshFiles()
}, nil)
}
func (gui *Gui) openFile(filename string) error {
if err := gui.OSCommand.OpenFile(filename); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
@@ -504,15 +492,181 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
return false
}
func (gui *Gui) handleSoftReset(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("SoftReset"), gui.Tr.SLocalize("ConfirmSoftReset"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.SoftReset("HEAD^"); err != nil {
return gui.createErrorPanel(g, err.Error())
}
type discardOption struct {
handler func(fileName *commands.File) error
description string
}
if err := gui.refreshCommits(gui.g); err != nil {
type discardAllOption struct {
handler func() error
description string
command string
}
// GetDisplayStrings is a function.
func (r *discardOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description}
}
// GetDisplayStrings is a function.
func (r *discardAllOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
}
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
file, err := gui.getSelectedFile(g)
if err != nil {
if err != gui.Errors.ErrNoFiles {
return err
}
return nil
}
options := []*discardOption{
{
description: gui.Tr.SLocalize("discardAllChanges"),
handler: func(file *commands.File) error {
return gui.GitCommand.DiscardAllFileChanges(file)
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func(file *commands.File) error {
return nil
},
},
}
if file.HasStagedChanges && file.HasUnstagedChanges {
discardUnstagedChanges := &discardOption{
description: gui.Tr.SLocalize("discardUnstagedChanges"),
handler: func(file *commands.File) error {
return gui.GitCommand.DiscardUnstagedFileChanges(file)
},
}
options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...)
}
handleMenuPress := func(index int) error {
file, err := gui.getSelectedFile(g)
if err != nil {
return err
}
if err := options[index].handler(file); err != nil {
return err
}
return gui.refreshFiles()
}, nil)
}
return gui.createMenu(file.Name, options, len(options), handleMenuPress)
}
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
options := []*discardAllOption{
{
description: gui.Tr.SLocalize("discardAllChangesToAllFiles"),
command: "reset --hard HEAD && git clean -fd",
handler: func() error {
return gui.GitCommand.ResetAndClean()
},
},
{
description: gui.Tr.SLocalize("discardAnyUnstagedChanges"),
command: "git checkout -- .",
handler: func() error {
return gui.GitCommand.DiscardAnyUnstagedFileChanges()
},
},
{
description: gui.Tr.SLocalize("discardUntrackedFiles"),
command: "git clean -fd",
handler: func() error {
return gui.GitCommand.RemoveUntrackedFiles()
},
},
{
description: gui.Tr.SLocalize("softReset"),
command: "git reset --soft HEAD",
handler: func() error {
return gui.GitCommand.ResetSoftHead()
},
},
{
description: gui.Tr.SLocalize("hardReset"),
command: "git reset --hard HEAD",
handler: func() error {
return gui.GitCommand.ResetHardHead()
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
},
},
}
handleMenuPress := func(index int) error {
if err := options[index].handler(); err != nil {
return err
}
return gui.refreshFiles()
}
return gui.createMenu("", options, len(options), handleMenuPress)
}
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), func(g *gocui.Gui, v *gocui.View) error {
command := gui.trimmedContent(v)
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
return gui.Errors.ErrSubProcess
})
}
type stashOption struct {
description string
handler func() error
}
// GetDisplayStrings is a function.
func (o *stashOption) GetDisplayStrings(isFocused bool) []string {
return []string{o.description}
}
func (gui *Gui) handleCreateStashMenu(g *gocui.Gui, v *gocui.View) error {
options := []*stashOption{
{
description: gui.Tr.SLocalize("stashAllChanges"),
handler: func() error {
return gui.handleStashSave(gui.GitCommand.StashSave)
},
},
{
description: gui.Tr.SLocalize("stashStagedChanges"),
handler: func() error {
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
},
},
{
description: gui.Tr.SLocalize("cancel"),
handler: func() error {
return nil
},
},
}
handleMenuPress := func(index int) error {
return options[index].handler()
}
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), options, len(options), handleMenuPress)
}
func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
return gui.handleStashSave(gui.GitCommand.StashSave)
}
+198 -101
View File
@@ -1,14 +1,15 @@
package gui
import (
"fmt"
"io/ioutil"
"math"
"os"
"sync"
// "io"
// "io/ioutil"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
@@ -103,7 +104,8 @@ type branchPanelState struct {
}
type commitPanelState struct {
SelectedLine int
SelectedLine int
SpecificDiffMode bool
}
type stashPanelState struct {
@@ -114,14 +116,19 @@ type menuPanelState struct {
SelectedLine int
}
type commitFilesPanelState struct {
SelectedLine int
}
type panelStates struct {
Files *filePanelState
Branches *branchPanelState
Commits *commitPanelState
Stash *stashPanelState
Menu *menuPanelState
Staging *stagingPanelState
Merging *mergingPanelState
Files *filePanelState
Branches *branchPanelState
Commits *commitPanelState
Stash *stashPanelState
Menu *menuPanelState
Staging *stagingPanelState
Merging *mergingPanelState
CommitFiles *commitFilesPanelState
}
type guiState struct {
@@ -129,6 +136,9 @@ type guiState struct {
Branches []*commands.Branch
Commits []*commands.Commit
StashEntries []*commands.StashEntry
CommitFiles []*commands.CommitFile
DiffEntries []*commands.Commit
MenuItemCount int // can't store the actual list because it's of interface{} type
PreviousView string
Platform commands.Platform
Updating bool
@@ -147,13 +157,15 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
Commits: make([]*commands.Commit, 0),
CherryPickedCommits: make([]*commands.Commit, 0),
StashEntries: make([]*commands.StashEntry, 0),
DiffEntries: make([]*commands.Commit, 0),
Platform: *oSCommand.Platform,
Panels: &panelStates{
Files: &filePanelState{SelectedLine: -1},
Branches: &branchPanelState{SelectedLine: 0},
Commits: &commitPanelState{SelectedLine: -1},
Stash: &stashPanelState{SelectedLine: -1},
Menu: &menuPanelState{SelectedLine: 0},
Files: &filePanelState{SelectedLine: -1},
Branches: &branchPanelState{SelectedLine: 0},
Commits: &commitPanelState{SelectedLine: -1},
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
Stash: &stashPanelState{SelectedLine: -1},
Menu: &menuPanelState{SelectedLine: 0},
Merging: &mergingPanelState{
ConflictIndex: 0,
ConflictTop: true,
@@ -213,20 +225,21 @@ func max(a, b int) int {
// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
var focusedView *gocui.View
var previousView *gocui.View
return func(g *gocui.Gui) error {
v := gui.g.CurrentView()
if v != focusedView {
if err := gui.onFocusChange(); err != nil {
newView := gui.g.CurrentView()
if err := gui.onFocusChange(); err != nil {
return err
}
// for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
if err := gui.onFocusLost(previousView, newView); err != nil {
return err
}
if err := gui.onFocusLost(focusedView); err != nil {
if err := gui.onFocus(newView); err != nil {
return err
}
if err := gui.onFocus(v); err != nil {
return err
}
focusedView = v
previousView = newView
}
return nil
}
@@ -240,21 +253,24 @@ func (gui *Gui) onFocusChange() error {
return gui.setMainTitle()
}
func (gui *Gui) onFocusLost(v *gocui.View) error {
func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
if v == nil {
return nil
}
if v.Name() == "branches" {
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
// inside renderListPanel it checks to see if the panel has focus
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
return err
}
} else if v.Name() == "main" {
// if we have lost focus to a popup panel, that's okay
if gui.popupPanelFocused() {
return nil
// if we have lost focus to a first-class panel, we need to do some cleanup
if err := gui.changeContext("main", "normal"); err != nil {
return err
}
if err := gui.changeContext("main", "normal"); err != nil {
} else if v.Name() == "commitFiles" {
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
return err
}
}
@@ -274,20 +290,75 @@ func (gui *Gui) onFocus(v *gocui.View) error {
func (gui *Gui) layout(g *gocui.Gui) error {
g.Highlight = true
width, height := g.Size()
information := gui.Config.GetVersion()
if gui.g.Mouse {
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
information = donate + " " + information
}
leftSideWidth := width / 3
statusFilesBoundary := 2
filesBranchesBoundary := 2 * height / 5
commitsBranchesBoundary := 3 * height / 5
optionsTop := height - 2
commitsStashBoundary := optionsTop - 3
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
minimumHeight := 18
minimumHeight := 9
minimumWidth := 10
if height < minimumHeight || width < minimumWidth {
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
if err != nil {
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
v.Wrap = true
_, _ = g.SetViewOnTop("limit")
}
return nil
}
currView := gui.g.CurrentView()
currentCyclebleView := gui.State.PreviousView
if currView != nil {
viewName := currView.Name()
usePreviouseView := true
for _, view := range cyclableViews {
if view == viewName {
currentCyclebleView = viewName
usePreviouseView = false
break
}
}
if usePreviouseView {
currentCyclebleView = gui.State.PreviousView
}
}
usableSpace := height - 7
extraSpace := usableSpace - (usableSpace/3)*3
vHeights := map[string]int{
"status": 3,
"files": (usableSpace / 3) + extraSpace,
"branches": usableSpace / 3,
"commits": usableSpace / 3,
"stash": 3,
"options": 1,
}
if height < 28 {
defaultHeight := 3
if height < 21 {
defaultHeight = 1
}
vHeights = map[string]int{
"status": defaultHeight,
"files": defaultHeight,
"branches": defaultHeight,
"commits": defaultHeight,
"stash": defaultHeight,
"options": defaultHeight,
}
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
}
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
leftSideWidth := width / 3
appStatus := gui.statusManager.getStatusString()
appStatusOptionsBoundary := 0
@@ -300,22 +371,10 @@ func (gui *Gui) layout(g *gocui.Gui) error {
panelSpacing = 0
}
if height < minimumHeight || width < minimumWidth {
v, err := g.SetView("limit", 0, 0, max(width-1, 2), max(height-1, 2), 0)
if err != nil {
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
v.Wrap = true
g.SetViewOnTop("limit")
}
return nil
}
_, _ = g.SetViewOnBottom("limit")
g.DeleteView("limit")
v, err := g.SetView("main", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT)
v, err := g.SetView("main", leftSideWidth+panelSpacing, 0, width-1, height-2, gocui.LEFT)
if err != nil {
if err.Error() != "unknown view" {
return err
@@ -325,7 +384,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.FgColor = gocui.ColorWhite
}
if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil {
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -333,7 +392,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.FgColor = gocui.ColorWhite
}
filesView, err := g.SetView("files", 0, statusFilesBoundary+panelSpacing, leftSideWidth, filesBranchesBoundary, gocui.TOP|gocui.BOTTOM)
filesView, err := g.SetViewBeneath("files", "status", vHeights["files"])
if err != nil {
if err.Error() != "unknown view" {
return err
@@ -343,7 +402,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
v.FgColor = gocui.ColorWhite
}
branchesView, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM)
branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
if err != nil {
if err.Error() != "unknown view" {
return err
@@ -352,7 +411,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
branchesView.FgColor = gocui.ColorWhite
}
commitsView, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM)
if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
if err.Error() != "unknown view" {
return err
}
v.Title = gui.Tr.SLocalize("CommitFiles")
v.FgColor = gocui.ColorWhite
}
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
if err != nil {
if err.Error() != "unknown view" {
return err
@@ -361,7 +428,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
commitsView.FgColor = gocui.ColorWhite
}
stashView, err := g.SetView("stash", 0, commitsStashBoundary+panelSpacing, leftSideWidth, optionsTop, gocui.TOP|gocui.RIGHT)
stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
if err != nil {
if err.Error() != "unknown view" {
return err
@@ -370,7 +437,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
stashView.FgColor = gocui.ColorWhite
}
if v, err := g.SetView("options", appStatusOptionsBoundary-1, optionsTop, optionsVersionBoundary-1, optionsTop+2, 0); err != nil {
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -382,7 +449,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
if gui.getCommitMessageView() == nil {
// doesn't matter where this view starts because it will be hidden
if commitMessageView, err := g.SetView("commitMessage", 0, 0, width/2, height/2, 0); err != nil {
if commitMessageView, err := g.SetView("commitMessage", width, height, width*2, height*2, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -390,13 +457,12 @@ 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)
}
}
if check, _ := g.View("credentials"); check == nil {
// doesn't matter where this view starts because it will be hidden
if credentialsView, err := g.SetView("credentials", 0, 0, width/2, height/2, 0); err != nil {
if credentialsView, err := g.SetView("credentials", width, height, width*2, height*2, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -407,11 +473,10 @@ func (gui *Gui) layout(g *gocui.Gui) error {
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
credentialsView.FgColor = gocui.ColorWhite
credentialsView.Editable = true
credentialsView.Editor = gocui.EditorFunc(gui.simpleEditor)
}
}
if appStatusView, err := g.SetView("appStatus", -1, optionsTop, width, optionsTop+2, 0); err != nil {
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -423,7 +488,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
if v, err := g.SetView("information", optionsVersionBoundary-1, optionsTop, width, optionsTop+2, 0); err != nil {
if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
if err.Error() != "unknown view" {
return err
}
@@ -434,47 +499,41 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return err
}
// these are only called once (it's a place to put all the things you want
// to happen on startup after the screen is first rendered)
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
if err := gui.updateRecentRepoList(); err != nil {
// doing this here because it'll only happen once
if err := gui.loadNewRepo(); err != nil {
return err
}
gui.waitForIntro.Done()
if _, err := gui.g.SetCurrentView(filesView.Name()); err != nil {
return err
}
if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if err := gui.switchFocus(g, nil, filesView); err != nil {
return err
}
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
if err := gui.promptAnonymousReporting(); err != nil {
return err
}
}
}
listViews := map[*gocui.View]int{
filesView: gui.State.Panels.Files.SelectedLine,
branchesView: gui.State.Panels.Branches.SelectedLine,
commitsView: gui.State.Panels.Commits.SelectedLine,
stashView: gui.State.Panels.Stash.SelectedLine,
if gui.g.CurrentView() == nil {
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
return err
}
if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil {
return err
}
}
type listViewState struct {
selectedLine int
lineCount int
}
listViews := map[*gocui.View]listViewState{
filesView: {selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
branchesView: {selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
commitsView: {selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
stashView: {selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries)},
}
// menu view might not exist so we check to be safe
if menuView, err := gui.g.View("menu"); err == nil {
listViews[menuView] = gui.State.Panels.Menu.SelectedLine
listViews[menuView] = listViewState{selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}
}
for view, selectedLine := range listViews {
for view, state := range listViews {
// check if the selected line is now out of view and if so refocus it
if err := gui.focusPoint(0, selectedLine, view); err != nil {
if err := gui.focusPoint(0, state.selectedLine, state.lineCount, view); err != nil {
return err
}
}
@@ -486,6 +545,25 @@ func (gui *Gui) layout(g *gocui.Gui) error {
return gui.resizeCurrentPopupPanel(g)
}
func (gui *Gui) loadNewRepo() error {
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
if err := gui.updateRecentRepoList(); err != nil {
return err
}
gui.waitForIntro.Done()
if err := gui.refreshSidePanels(gui.g); err != nil {
return err
}
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
if err := gui.promptAnonymousReporting(); err != nil {
return err
}
}
return nil
}
func (gui *Gui) promptAnonymousReporting() error {
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
gui.waitForIntro.Done()
@@ -605,14 +683,9 @@ func (gui *Gui) RunWithSubprocesses() error {
} else if err == gui.Errors.ErrSwitchRepo {
continue
} else if err == gui.Errors.ErrSubProcess {
gui.SubProcess.Stdin = os.Stdin
gui.SubProcess.Stdout = os.Stdout
gui.SubProcess.Stderr = os.Stderr
gui.SubProcess.Run()
gui.SubProcess.Stdout = ioutil.Discard
gui.SubProcess.Stderr = ioutil.Discard
gui.SubProcess.Stdin = nil
gui.SubProcess = nil
if err := gui.runCommand(); err != nil {
return err
}
} else {
return err
}
@@ -621,6 +694,30 @@ func (gui *Gui) RunWithSubprocesses() error {
return nil
}
func (gui *Gui) runCommand() error {
gui.SubProcess.Stdout = os.Stdout
gui.SubProcess.Stderr = os.Stdout
gui.SubProcess.Stdin = os.Stdin
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue))
if err := gui.SubProcess.Run(); err != nil {
// not handling the error explicitly because usually we're going to see it
// in the output anyway
gui.Log.Error(err)
}
gui.SubProcess.Stdout = ioutil.Discard
gui.SubProcess.Stderr = ioutil.Discard
gui.SubProcess.Stdin = nil
gui.SubProcess = nil
fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.SLocalize("pressEnterToReturn"), color.FgGreen))
fmt.Scanln() // wait for enter press
return nil
}
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
if gui.State.Updating {
return gui.createUpdateQuitConfirmation(g, v)
+108 -24
View File
@@ -13,6 +13,7 @@ type Binding struct {
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
Modifier gocui.Modifier
Description string
Alternative string
}
// GetDisplayStrings returns the display string of a file
@@ -28,6 +29,12 @@ func (b *Binding) GetKey() string {
case rune:
key = int(b.Key.(rune))
case gocui.Key:
if b.Key.(gocui.Key) == gocui.KeyCtrlJ {
return "ctrl+j"
}
if b.Key.(gocui.Key) == gocui.KeyCtrlK {
return "ctrl+k"
}
key = int(b.Key.(gocui.Key))
}
@@ -74,14 +81,26 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.quit,
}, {
ViewName: "",
Key: gocui.KeyPgup,
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
Alternative: "fn+up",
}, {
ViewName: "",
Key: gocui.KeyPgdn,
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
Alternative: "fn+down",
}, {
ViewName: "",
Key: gocui.KeyPgup,
Key: 'K',
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
}, {
ViewName: "",
Key: gocui.KeyPgdn,
Key: 'J',
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
}, {
@@ -154,6 +173,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCommitPress,
Description: gui.Tr.SLocalize("CommitChanges"),
},
{
ViewName: "files",
Key: 'w',
Modifier: gocui.ModNone,
Handler: gui.handleWIPCommitPress,
Description: gui.Tr.SLocalize("commitChangesWithoutHook"),
}, {
ViewName: "files",
Key: 'A',
@@ -176,8 +202,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "files",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleFileRemove,
Description: gui.Tr.SLocalize("removeFile"),
Handler: gui.handleCreateDiscardMenu,
Description: gui.Tr.SLocalize("viewDiscardOptions"),
}, {
ViewName: "files",
Key: 'e',
@@ -202,18 +228,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
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: 's',
Modifier: gocui.ModNone,
Handler: gui.handleSoftReset,
Description: gui.Tr.SLocalize("softReset"),
Handler: gui.handleStashChanges,
Description: gui.Tr.SLocalize("stashAllChanges"),
}, {
ViewName: "files",
Key: 'S',
Modifier: gocui.ModNone,
Handler: gui.handleCreateStashMenu,
Description: gui.Tr.SLocalize("viewStashOptions"),
}, {
ViewName: "files",
Key: 'a',
@@ -230,8 +256,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "files",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleResetAndClean,
Description: gui.Tr.SLocalize("resetHard"),
Handler: gui.handleCreateResetMenu,
Description: gui.Tr.SLocalize("viewResetOptions"),
}, {
ViewName: "files",
Key: gocui.KeyEnter,
@@ -244,6 +270,12 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleGitFetch,
Description: gui.Tr.SLocalize("fetch"),
}, {
ViewName: "files",
Key: 'X',
Modifier: gocui.ModNone,
Handler: gui.handleCustomCommand,
Description: gui.Tr.SLocalize("executeCustomCommand"),
}, {
ViewName: "branches",
Key: gocui.KeySpace,
@@ -320,7 +352,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "commits",
Key: 'g',
Modifier: gocui.ModNone,
Handler: gui.handleResetToCommit,
Handler: gui.handleCreateCommitResetMenu,
Description: gui.Tr.SLocalize("resetToThisCommit"),
}, {
ViewName: "commits",
@@ -328,6 +360,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCommitFixup,
Description: gui.Tr.SLocalize("fixupCommit"),
}, {
ViewName: "commits",
Key: 'F',
Modifier: gocui.ModNone,
Handler: gui.handleCreateFixupCommit,
Description: gui.Tr.SLocalize("createFixupCommit"),
}, {
ViewName: "commits",
Key: 'S',
Modifier: gocui.ModNone,
Handler: gui.handleSquashAllAboveFixupCommits,
Description: gui.Tr.SLocalize("squashAboveCommits"),
}, {
ViewName: "commits",
Key: 'd',
@@ -336,13 +380,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Description: gui.Tr.SLocalize("deleteCommit"),
}, {
ViewName: "commits",
Key: 'J',
Key: gocui.KeyCtrlJ,
Modifier: gocui.ModNone,
Handler: gui.handleCommitMoveDown,
Description: gui.Tr.SLocalize("moveDownCommit"),
}, {
ViewName: "commits",
Key: 'K',
Key: gocui.KeyCtrlK,
Modifier: gocui.ModNone,
Handler: gui.handleCommitMoveUp,
Description: gui.Tr.SLocalize("moveUpCommit"),
@@ -388,6 +432,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.HandlePasteCommits,
Description: gui.Tr.SLocalize("pasteCommits"),
}, {
ViewName: "commits",
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
Handler: gui.handleSwitchToCommitFilesPanel,
Description: gui.Tr.SLocalize("viewCommitFiles"),
}, {
ViewName: "commits",
Key: gocui.KeySpace,
Modifier: gocui.ModNone,
Handler: gui.handleToggleDiffCommit,
Description: gui.Tr.SLocalize("CommitsDiff"),
}, {
ViewName: "stash",
Key: gocui.KeySpace,
@@ -441,10 +497,35 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleDonate,
}, {
ViewName: "commitFiles",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleSwitchToCommitsPanel,
Description: gui.Tr.SLocalize("goBack"),
}, {
ViewName: "commitFiles",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleCheckoutCommitFile,
Description: gui.Tr.SLocalize("checkoutCommitFile"),
}, {
ViewName: "commitFiles",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleDiscardOldFileChange,
Description: gui.Tr.SLocalize("discardOldFileChange"),
},
{
ViewName: "commitFiles",
Key: 'o',
Modifier: gocui.ModNone,
Handler: gui.handleOpenOldCommitFile,
Description: gui.Tr.SLocalize("openFile"),
},
}
for _, viewName := range []string{"status", "branches", "files", "commits", "stash", "menu"} {
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "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},
@@ -459,12 +540,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
nextLine func(*gocui.Gui, *gocui.View) error
focus func(*gocui.Gui, *gocui.View) error
}{
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, focus: gui.handleMenuSelect},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine, focus: gui.handleFilesFocus},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine, focus: gui.handleBranchSelect},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine, focus: gui.handleCommitSelect},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine, focus: gui.handleStashEntrySelect},
"status": {focus: gui.handleStatusSelect},
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, focus: gui.handleMenuSelect},
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine, focus: gui.handleFilesFocus},
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine, focus: gui.handleBranchSelect},
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine, focus: gui.handleCommitSelect},
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine, focus: gui.handleStashEntrySelect},
"status": {focus: gui.handleStatusSelect},
"commitFiles": {prevLine: gui.handleCommitFilesPrevLine, nextLine: gui.handleCommitFilesNextLine, focus: gui.handleCommitFileSelect},
}
for viewName, functions := range listPanelMap {
@@ -516,12 +598,14 @@ func (gui *Gui) GetContextMap() map[string]map[string][]*Binding {
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
Description: gui.Tr.SLocalize("ScrollDown"),
Alternative: "fn+up",
}, {
ViewName: "main",
Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
Description: gui.Tr.SLocalize("ScrollUp"),
Alternative: "fn+down",
},
},
"staging": {
+5 -2
View File
@@ -10,7 +10,7 @@ import (
// list panel functions
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, v)
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
}
func (gui *Gui) handleMenuNextLine(g *gocui.Gui, v *gocui.View) error {
@@ -51,8 +51,9 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
return gui.returnFocus(g, v)
}
func (gui *Gui) createMenu(title string, items interface{}, handlePress func(int) error) error {
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
isFocused := gui.g.CurrentView().Name() == "menu"
gui.State.MenuItemCount = itemCount
list, err := utils.RenderList(items, isFocused)
if err != nil {
return err
@@ -81,6 +82,8 @@ func (gui *Gui) createMenu(title string, items interface{}, handlePress func(int
}
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter} {
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
return err
}
+1 -1
View File
@@ -49,5 +49,5 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
return bindings[index].Handler(g, v)
}
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, handleMenuPress)
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, len(bindings), handleMenuPress)
}
+1 -1
View File
@@ -38,7 +38,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
title = gui.Tr.SLocalize("RebaseOptionsTitle")
}
return gui.createMenu(title, options, handleMenuPress)
return gui.createMenu(title, options, len(options), handleMenuPress)
}
func (gui *Gui) genericMergeCommand(command string) error {
+1 -1
View File
@@ -44,7 +44,7 @@ func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSwitchRepo
}
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, handleMenuPress)
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, len(recentRepos), handleMenuPress)
}
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
+5 -6
View File
@@ -31,7 +31,7 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
if stashEntry == nil {
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
}
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, v); err != nil {
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries), v); err != nil {
return err
}
go func() {
@@ -130,16 +130,15 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
return gui.refreshFiles()
}
func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error {
func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
}
gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil {
return gui.createPromptPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
if err := stashFunc(gui.trimmedContent(v)); err != nil {
gui.createErrorPanel(g, err.Error())
}
gui.refreshStashEntries(g)
return gui.refreshFiles()
})
return nil
}
+1 -1
View File
@@ -61,7 +61,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
[]string{
lazygitTitle(),
"Copyright (c) 2018 Jesse Duffield",
"Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md",
"Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings",
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
"Tutorial: https://youtu.be/VDXvbHZYeKY",
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
+47 -50
View File
@@ -22,6 +22,7 @@ func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
if err := gui.refreshCommits(g); err != nil {
return err
}
return gui.refreshStashEntries(g)
}
@@ -30,8 +31,13 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] {
focusedViewName = cyclableViews[0]
} else {
// if we're in the commitFiles view we'll act like we're in the commits view
viewName := v.Name()
if viewName == "commitFiles" {
viewName = "commits"
}
for i := range cyclableViews {
if v.Name() == cyclableViews[i] {
if viewName == cyclableViews[i] {
focusedViewName = cyclableViews[i+1]
break
}
@@ -39,7 +45,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
message := gui.Tr.TemplateLocalize(
"IssntListOfViews",
Teml{
"name": v.Name(),
"name": viewName,
},
)
gui.Log.Info(message)
@@ -59,8 +65,13 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
if v == nil || v.Name() == cyclableViews[0] {
focusedViewName = cyclableViews[len(cyclableViews)-1]
} else {
// if we're in the commitFiles view we'll act like we're in the commits view
viewName := v.Name()
if viewName == "commitFiles" {
viewName = "commits"
}
for i := range cyclableViews {
if v.Name() == cyclableViews[i] {
if viewName == cyclableViews[i] {
focusedViewName = cyclableViews[i-1] // TODO: make this work properly
break
}
@@ -68,7 +79,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
message := gui.Tr.TemplateLocalize(
"IssntListOfViews",
Teml{
"name": v.Name(),
"name": viewName,
},
)
gui.Log.Info(message)
@@ -95,6 +106,8 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
return gui.handleBranchSelect(g, v)
case "commits":
return gui.handleCommitSelect(g, v)
case "commitFiles":
return gui.handleCommitFileSelect(g, v)
case "stash":
return gui.handleStashEntrySelect(g, v)
case "confirmation":
@@ -129,15 +142,10 @@ func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error {
// pass in oldView = nil if you don't want to be able to return to your old view
// TODO: move some of this logic into our onFocusLost and onFocus hooks
func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
// we assume we'll never want to return focus to a confirmation panel i.e.
// we should never stack confirmation panels
if oldView != nil && oldView.Name() != "confirmation" {
// second class panels should never have focus restored to them because
// once they lose focus they are effectively 'destroyed'
secondClassPanels := []string{"confirmation", "menu"}
if !utils.IncludesString(secondClassPanels, oldView.Name()) {
gui.State.PreviousView = oldView.Name()
}
// we assume we'll never want to return focus to a popup panel i.e.
// we should never stack popup panels
if oldView != nil && !gui.isPopupPanel(oldView.Name()) {
gui.State.PreviousView = oldView.Name()
}
gui.Log.Info("setting highlight to true for view" + newView.Name())
@@ -165,49 +173,37 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
}
func (gui *Gui) resetOrigin(v *gocui.View) error {
if err := v.SetCursor(0, 0); err != nil {
return err
}
_ = v.SetCursor(0, 0)
return v.SetOrigin(0, 0)
}
// if the cursor down past the last item, move it to the last line
func (gui *Gui) focusPoint(cx int, cy int, v *gocui.View) error {
if cy < 0 {
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
if cy < 0 || cy > lineCount {
return nil
}
ox, oy := v.Origin()
_, height := v.Size()
ly := height - 1
if ly == -1 {
ly = 0
}
// if line is above origin, move origin and set cursor to zero
// if line is below origin + height, move origin and set cursor to max
// otherwise set cursor to value - origin
if ly > v.LinesHeight() {
if err := v.SetCursor(cx, cy); err != nil {
return err
}
if err := v.SetOrigin(ox, 0); err != nil {
return err
}
if ly > lineCount {
_ = v.SetCursor(cx, cy)
_ = v.SetOrigin(ox, 0)
} else if cy < oy {
if err := v.SetCursor(cx, 0); err != nil {
return err
}
if err := v.SetOrigin(ox, cy); err != nil {
return err
}
_ = v.SetCursor(cx, 0)
_ = v.SetOrigin(ox, cy)
} else if cy > oy+ly {
if err := v.SetCursor(cx, ly); err != nil {
return err
}
if err := v.SetOrigin(ox, cy-ly); err != nil {
return err
}
_ = v.SetCursor(cx, ly)
_ = v.SetOrigin(ox, cy-ly)
} else {
if err := v.SetCursor(cx, cy-oy); err != nil {
return err
}
_ = v.SetCursor(cx, cy-oy)
}
return nil
}
@@ -283,6 +279,11 @@ func (gui *Gui) getStashView() *gocui.View {
return v
}
func (gui *Gui) getCommitFilesView() *gocui.View {
v, _ := gui.g.View("commitFiles")
return v
}
func (gui *Gui) trimmedContent(v *gocui.View) string {
return strings.TrimSpace(v.Buffer())
}
@@ -294,7 +295,7 @@ func (gui *Gui) currentViewName() string {
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
v := g.CurrentView()
if v.Name() == "commitMessage" || v.Name() == "credentials" || v.Name() == "confirmation" {
if gui.isPopupPanel(v.Name()) {
return gui.resizePopupPanel(g, v)
}
return nil
@@ -386,14 +387,10 @@ func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error {
return err
}
func (gui *Gui) isPopupPanel(viewName string) bool {
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
}
func (gui *Gui) popupPanelFocused() bool {
viewNames := []string{"commitMessage",
"credentials",
"menu"}
for _, viewName := range viewNames {
if gui.currentViewName() == viewName {
return true
}
}
return false
return gui.isPopupPanel(gui.currentViewName())
}
+178 -73
View File
@@ -28,6 +28,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitsTitle",
Other: "Commits",
}, &i18n.Message{
ID: "CommitsDiffTitle",
Other: "Commits (specific diff mode)",
}, &i18n.Message{
ID: "CommitsDiff",
Other: "select commit to diff with another commit",
}, &i18n.Message{
ID: "StashTitle",
Other: "Stash",
@@ -79,9 +85,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "execute",
Other: "uitvoeren",
}, &i18n.Message{
ID: "stashFiles",
Other: "stash-bestanden",
}, &i18n.Message{
ID: "open",
Other: "open",
@@ -157,9 +160,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "FileNoMergeCons",
Other: "Dit bestand heeft geen merge conflicten",
}, &i18n.Message{
ID: "SureResetHardHead",
Other: "Weet je het zeker dat je `reset --hard HEAD` en `clean -fd` wil uitvoeren? Het kan dat je hierdoor bestanden verliest",
}, &i18n.Message{
ID: "SureTo",
Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijderd)",
@@ -340,9 +340,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CantCloseConfirmationPrompt",
Other: "Kon de bevestiging prompt niet sluiten: {{.error}}",
}, &i18n.Message{
ID: "ClearFilePanel",
Other: "maak bestandsvenster leeg",
}, &i18n.Message{
ID: "MergeAborted",
Other: "Merge afgebroken",
@@ -382,9 +379,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "GitconfigParseErr",
Other: `Gogit kon je gitconfig bestand niet goed parsen door de aanwezigheid van losstaande '\' tekens. Het weghalen van deze tekens zou het probleem moeten oplossen. `,
}, &i18n.Message{
ID: "removeFile",
Other: `Verwijder als untracked / uitchecken wordt gevolgd (ga weg)`,
}, &i18n.Message{
ID: "editFile",
Other: `verander bestand`,
@@ -397,9 +391,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "refreshFiles",
Other: `refresh bestanden`,
}, &i18n.Message{
ID: "resetHard",
Other: `harde reset and verwijderen ongevolgde bestanden`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `merge in met huidige checked out branch`,
@@ -423,7 +414,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: `fetch`,
}, &i18n.Message{
ID: "NoAutomaticGitFetchTitle",
Other: `Geen automatiese git fetch`,
Other: `Geen automatische git fetch`,
}, &i18n.Message{
ID: "NoAutomaticGitFetchBody",
Other: `Lazygit kan niet "git fetch" uitvoeren in een privé repository, gebruik f in het branches paneel om "git fetch" manueel uit te voeren`,
@@ -459,40 +450,34 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: "Merging",
}, &i18n.Message{
ID: "ConfirmRebase",
Other: "Are you sure you want to rebase {{.checkedOutBranch}} onto {{.selectedBranch}}?",
Other: "Weet je zeker dat je {{.checkedOutBranch}} op {{.selectedBranch}} wil rebasen?",
}, &i18n.Message{
ID: "ConfirmMerge",
Other: "Are you sure you want to merge {{.selectedBranch}} into {{.checkedOutBranch}}?",
Other: "Weet je zeker dat je {{.selectedBranch}} in {{.checkedOutBranch}} wil mergen?",
}, &i18n.Message{
ID: "FwdNoUpstream",
Other: "Cannot fast-forward a branch with no upstream",
Other: "Kan niet de branch vooruitspoelen zonder upstream",
}, &i18n.Message{
ID: "ErrorOccurred",
Other: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazygit/issues",
Other: "Er is iets fout gegaan! Zou je hier een issue aan willen maken: https://github.com/jesseduffield/lazygit/issues",
}, &i18n.Message{
ID: "FwdCommitsToPush",
Other: "Cannot fast-forward a branch with commits to push",
Other: "Je kan niet vooruitspoelen als de branch geen nieuwe commits heeft",
}, &i18n.Message{
ID: "MainTitle",
Other: "Main",
Other: "Hoofd",
}, &i18n.Message{
ID: "NormalTitle",
Other: "Normal",
Other: "Normaal",
}, &i18n.Message{
ID: "softReset",
Other: "soft reset to last commit",
}, &i18n.Message{
ID: "SoftReset",
Other: "Soft reset",
}, &i18n.Message{
ID: "ConfirmSoftReset",
Other: "Are you sure you want to `reset --soft HEAD^`? The changes in your topmost commit will be placed in your working tree",
Other: "zacht reset",
}, &i18n.Message{
ID: "CantRebaseOntoSelf",
Other: "You cannot rebase a branch onto itself",
Other: "Je kan niet een branch rebasen op zichzelf",
}, &i18n.Message{
ID: "SureSquashThisCommit",
Other: "Are you sure you want to squash this commit into the commit below?",
Other: "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?",
}, &i18n.Message{
ID: "Squash",
Other: "Squash",
@@ -501,127 +486,130 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: "pick commit (when mid-rebase)",
}, &i18n.Message{
ID: "revertCommit",
Other: "revert commit",
Other: "commit omgedaan maken",
}, &i18n.Message{
ID: "deleteCommit",
Other: "delete commit",
Other: "verwijder commit",
}, &i18n.Message{
ID: "moveDownCommit",
Other: "move commit down one",
Other: "verplaats commit 1 omlaag",
}, &i18n.Message{
ID: "moveUpCommit",
Other: "move commit up one",
Other: "verplaats commit 1 omhoog",
}, &i18n.Message{
ID: "editCommit",
Other: "edit commit",
Other: "verander commit",
}, &i18n.Message{
ID: "amendToCommit",
Other: "amend commit with staged changes",
Other: "wijzig commit met staged veranderingen",
}, &i18n.Message{
ID: "FoundConflicts",
Other: "Damn, conflicts! To abort press 'esc', otherwise press 'enter'",
Other: "Conflicten!, Om af te breken druk 'esc', anders druk op 'enter'",
}, &i18n.Message{
ID: "FoundConflictsTitle",
Other: "Auto-merge failed",
Other: "Auto-merge mislukt",
}, &i18n.Message{
ID: "Undo",
Other: "undo",
Other: "ongedaan maken",
}, &i18n.Message{
ID: "PickHunk",
Other: "pick hunk",
}, &i18n.Message{
ID: "PickBothHunks",
Other: "pick both hunks",
Other: "pick beide hunks",
}, &i18n.Message{
ID: "ViewMergeRebaseOptions",
Other: "view merge/rebase options",
Other: "bekijk merge/rebase opties",
}, &i18n.Message{
ID: "NotMergingOrRebasing",
Other: "You are currently neither rebasing nor merging",
Other: "Je bent momenteel niet aan het rebasen of mergen",
}, &i18n.Message{
ID: "RecentRepos",
Other: "recent repositories",
}, &i18n.Message{
ID: "MergeOptionsTitle",
Other: "Merge Options",
Other: "Merge Opties",
}, &i18n.Message{
ID: "RebaseOptionsTitle",
Other: "Rebase Options",
Other: "Rebase Opties",
}, &i18n.Message{
ID: "ConflictsResolved",
Other: "all merge conflicts resolved. Continue?",
Other: "alle merge conflicten zijn opgelost. Wilt je verder gaan?",
}, &i18n.Message{
ID: "NoRoom",
Other: "Not enough room",
Other: "Niet genoeg ruimte",
}, &i18n.Message{
ID: "YouAreHere",
Other: "YOU ARE HERE",
Other: "JE BENT HIER",
}, &i18n.Message{
ID: "rewordNotSupported",
Other: "rewording commits while interactively rebasing is not currently supported",
Other: "herformatteren van commits in interactief rebasen is nog niet ondersteund",
}, &i18n.Message{
ID: "cherryPickCopy",
Other: "copy commit (cherry-pick)",
Other: "kopiëer commit (cherry-pick)",
}, &i18n.Message{
ID: "cherryPickCopyRange",
Other: "copy commit range (cherry-pick)",
Other: "kopiëer commit reeks (cherry-pick)",
}, &i18n.Message{
ID: "pasteCommits",
Other: "paste commits (cherry-pick)",
Other: "plak commits (cherry-pick)",
}, &i18n.Message{
ID: "SureCherryPick",
Other: "Are you sure you want to cherry-pick the copied commits onto this branch?",
Other: "Weet je zeker dat je de gekopieerde commits naar deze branch wil cherry-picken?",
}, &i18n.Message{
ID: "CherryPick",
Other: "Cherry-Pick",
}, &i18n.Message{
ID: "CannotRebaseOntoFirstCommit",
Other: "You cannot interactive rebase onto the first commit",
Other: "Je kan niet interactief rebasen naar de eerste commit",
}, &i18n.Message{
ID: "CannotSquashOntoSecondCommit",
Other: "Je kan niet een squash/fixup doen naar de 2de commit",
}, &i18n.Message{
ID: "Donate",
Other: "Donate",
Other: "Doneer",
}, &i18n.Message{
ID: "PrevLine",
Other: "select previous line",
Other: "selecteer de vorige lijn",
}, &i18n.Message{
ID: "NextLine",
Other: "select next line",
Other: "selecteer de volgende lijn",
}, &i18n.Message{
ID: "PrevHunk",
Other: "select previous hunk",
Other: "selecteer de vorige hunk",
}, &i18n.Message{
ID: "NextHunk",
Other: "select next hunk",
Other: "selecteer de volgende hunk",
}, &i18n.Message{
ID: "PrevConflict",
Other: "select previous conflict",
Other: "selecteer voorgaand conflict",
}, &i18n.Message{
ID: "NextConflict",
Other: "select next conflict",
Other: "selecteer volgende conflict",
}, &i18n.Message{
ID: "SelectTop",
Other: "select top hunk",
Other: "selecteer bovenste hunk",
}, &i18n.Message{
ID: "SelectBottom",
Other: "select bottom hunk",
Other: "selecteer onderste hunk",
}, &i18n.Message{
ID: "ScrollDown",
Other: "scroll down",
Other: "scroll omlaag",
}, &i18n.Message{
ID: "ScrollUp",
Other: "scroll up",
Other: "scroll omhoog",
}, &i18n.Message{
ID: "AmendCommitTitle",
Other: "Amend Commit",
Other: "Commit wijzigen",
}, &i18n.Message{
ID: "AmendCommitPrompt",
Other: "Are you sure you want to amend this commit with your staged files?",
Other: "Weet je zeker dat je deze commit wil wijzigen met de vorige staged bestanden?",
}, &i18n.Message{
ID: "DeleteCommitTitle",
Other: "Delete Commit",
Other: "Verwijder Commit",
}, &i18n.Message{
ID: "DeleteCommitPrompt",
Other: "Are you sure you want to delete this commit?",
Other: "Weet je zeker dat je deze commit wil verwijderen?",
}, &i18n.Message{
ID: "SquashingStatus",
Other: "squashing",
@@ -630,19 +618,136 @@ func addDutch(i18nObject *i18n.Bundle) error {
Other: "fixing up",
}, &i18n.Message{
ID: "DeletingStatus",
Other: "deleting",
Other: "verwijderen",
}, &i18n.Message{
ID: "MovingStatus",
Other: "moving",
Other: "verplaatsen",
}, &i18n.Message{
ID: "RebasingStatus",
Other: "rebasing",
}, &i18n.Message{
ID: "AmendingStatus",
Other: "amending",
Other: "wijzigen",
}, &i18n.Message{
ID: "CherryPickingStatus",
Other: "cherry-picking",
}, &i18n.Message{
ID: "CommitFiles",
Other: "Commit bestanden",
}, &i18n.Message{
ID: "viewCommitFiles",
Other: "bekijk gecommite bestanden",
}, &i18n.Message{
ID: "CommitFilesTitle",
Other: "Commit bestanden",
}, &i18n.Message{
ID: "goBack",
Other: "ga terug",
}, &i18n.Message{
ID: "NoCommiteFiles",
Other: "Geen bestanden voor deze commit",
}, &i18n.Message{
ID: "checkoutCommitFile",
Other: "bestand uitchecken",
}, &i18n.Message{
ID: "discardOldFileChange",
Other: "uitsluit deze commit zijn veranderingen aan dit bestand",
}, &i18n.Message{
ID: "DiscardFileChangesTitle",
Other: "uitsluit bestand zijn veranderingen",
}, &i18n.Message{
ID: "DiscardFileChangesPrompt",
Other: "Weet je zeker dat je de wijzigingen van deze commit in dit bestand wilt weggooien? Als dit bestand is gecreëerd in deze commit dan zal dit bestand worden verwijdert",
}, &i18n.Message{
ID: "DisabledForGPG",
Other: "Onderdelen niet beschikbaar voor gebruikers die GPG gebruiken",
}, &i18n.Message{
ID: "CreateRepo",
Other: "Niet in een git repository. Creëer een nieuwe git repository? (y/n): ",
}, &i18n.Message{
ID: "AutoStashTitle",
Other: "Autostash?",
}, &i18n.Message{
ID: "AutoStashPrompt",
Other: "Je moet je veranderingen stashen en poppen om ze over te bregen. Dit automatisch doen? (enter/esc)",
}, &i18n.Message{
ID: "StashPrefix",
Other: "Auto-stashing veranderingen voor ",
}, &i18n.Message{
ID: "viewDiscardOptions",
Other: "bekijk 'veranderingen ongedaan maken' opties",
}, &i18n.Message{
ID: "cancel",
Other: "anuleren",
}, &i18n.Message{
ID: "discardAllChanges",
Other: "negeer alle wijzigingen",
}, &i18n.Message{
ID: "discardUnstagedChanges",
Other: "negeer unstaged wijzigingen",
}, &i18n.Message{
ID: "discardAllChangesToAllFiles",
Other: "verwijder werkende tree",
}, &i18n.Message{
ID: "discardAnyUnstagedChanges",
Other: "discard unstaged changes",
}, &i18n.Message{
ID: "discardUntrackedFiles",
Other: "negeer niet-gevonden bestanden",
}, &i18n.Message{
ID: "viewResetOptions",
Other: `bekijk reset opties`,
}, &i18n.Message{
ID: "hardReset",
Other: "harde reset",
}, &i18n.Message{
ID: "createFixupCommit",
Other: `creëer fixup commit voor deze commit`,
}, &i18n.Message{
ID: "squashAboveCommits",
Other: `squash bovenstaande commits`,
}, &i18n.Message{
ID: "SquashAboveCommits",
Other: `Squash bovenstaande commits`,
}, &i18n.Message{
ID: "SureSquashAboveCommits",
Other: `Weet je zeker dat je alles wil squash/fixup! voor de bovenstaand commits {{.commit}}?`,
}, &i18n.Message{
ID: "CreateFixupCommit",
Other: `Creëer fixup commit`,
}, &i18n.Message{
ID: "SureCreateFixupCommit",
Other: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`,
}, &i18n.Message{
ID: "executeCustomCommand",
Other: "voor aangepast commando uit",
}, &i18n.Message{
ID: "CustomCommand",
Other: "Aangepast commando:",
}, &i18n.Message{
ID: "commitChangesWithoutHook",
Other: "commit veranderingen zonder pre-commit hook",
}, &i18n.Message{
ID: "SkipHookPrefixNotConfigured",
Other: "Je hebt nog niet een commit bericht voorvoegsel ingesteld voor het overslaan van hooks. Set `git.skipHookPrefix = 'WIP'` in je config",
}, &i18n.Message{
ID: "resetTo",
Other: `reset to`,
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
}, &i18n.Message{
ID: "viewStashOptions",
Other: "view stash options",
}, &i18n.Message{
ID: "stashAllChanges",
Other: "stash-bestanden",
}, &i18n.Message{
ID: "stashStagedChanges",
Other: "stash staged changes",
}, &i18n.Message{
ID: "stashOptions",
Other: "Stash options",
},
)
}
+129 -24
View File
@@ -36,6 +36,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitsTitle",
Other: "Commits",
}, &i18n.Message{
ID: "CommitsDiffTitle",
Other: "Commits (specific diff mode)",
}, &i18n.Message{
ID: "CommitsDiff",
Other: "select commit to diff with another commit",
}, &i18n.Message{
ID: "StashTitle",
Other: "Stash",
@@ -99,12 +105,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "execute",
Other: "execute",
}, &i18n.Message{
ID: "stashFiles",
Other: "stash files",
}, &i18n.Message{
ID: "softReset",
Other: "soft reset to last commit",
}, &i18n.Message{
ID: "open",
Other: "open",
@@ -181,14 +181,8 @@ func addEnglish(i18nObject *i18n.Bundle) error {
ID: "FileNoMergeCons",
Other: "This file has no inline merge conflicts",
}, &i18n.Message{
ID: "SureResetHardHead",
Other: "Are you sure you want to `reset --hard HEAD` and `clean -fd`? You may lose changes",
}, &i18n.Message{
ID: "SoftReset",
Other: "Soft reset",
}, &i18n.Message{
ID: "ConfirmSoftReset",
Other: "Are you sure you want to `reset --soft HEAD^`? The changes in your topmost commit will be placed in your working tree",
ID: "softReset",
Other: "soft reset",
}, &i18n.Message{
ID: "SureTo",
Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?",
@@ -405,9 +399,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "NoChangedFiles",
Other: "No changed files",
}, &i18n.Message{
ID: "ClearFilePanel",
Other: "Clear file panel",
}, &i18n.Message{
ID: "MergeAborted",
Other: "Merge aborted",
@@ -447,9 +438,6 @@ 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`,
@@ -462,9 +450,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "refreshFiles",
Other: `refresh files`,
}, &i18n.Message{
ID: "resetHard",
Other: `reset hard and remove untracked files`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `merge into currently checked out branch`,
@@ -521,7 +506,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
Other: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
}, &i18n.Message{
ID: "FoundConflicts",
Other: "Damn, conflicts! To abort press 'esc', otherwise press 'enter'",
Other: "Conflicts! To abort press 'esc', otherwise press 'enter'",
}, &i18n.Message{
ID: "FoundConflictsTitle",
Other: "Auto-merge failed",
@@ -600,6 +585,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CannotRebaseOntoFirstCommit",
Other: "You cannot interactive rebase onto the first commit",
}, &i18n.Message{
ID: "CannotSquashOntoSecondCommit",
Other: "You cannot squash/fixup onto the second commit",
}, &i18n.Message{
ID: "Donate",
Other: "Donate",
@@ -666,6 +654,123 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CherryPickingStatus",
Other: "cherry-picking",
}, &i18n.Message{
ID: "CommitFiles",
Other: "Commit files",
}, &i18n.Message{
ID: "viewCommitFiles",
Other: "view commit's files",
}, &i18n.Message{
ID: "CommitFilesTitle",
Other: "Commit files",
}, &i18n.Message{
ID: "goBack",
Other: "go back",
}, &i18n.Message{
ID: "NoCommiteFiles",
Other: "No files for this commit",
}, &i18n.Message{
ID: "checkoutCommitFile",
Other: "checkout file",
}, &i18n.Message{
ID: "discardOldFileChange",
Other: "discard this commit's changes to this file",
}, &i18n.Message{
ID: "DiscardFileChangesTitle",
Other: "Discard file changes",
}, &i18n.Message{
ID: "DiscardFileChangesPrompt",
Other: "Are you sure you want to discard this commit's changes to this file? If this file was created in this commit, it will be deleted",
}, &i18n.Message{
ID: "DisabledForGPG",
Other: "Feature not available for users using GPG",
}, &i18n.Message{
ID: "CreateRepo",
Other: "Not in a git repository. Create a new git repository? (y/n): ",
}, &i18n.Message{
ID: "AutoStashTitle",
Other: "Autostash?",
}, &i18n.Message{
ID: "AutoStashPrompt",
Other: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
}, &i18n.Message{
ID: "StashPrefix",
Other: "Auto-stashing changes for ",
}, &i18n.Message{
ID: "viewDiscardOptions",
Other: "view 'discard changes' options",
}, &i18n.Message{
ID: "cancel",
Other: "cancel",
}, &i18n.Message{
ID: "discardAllChanges",
Other: "discard all changes",
}, &i18n.Message{
ID: "discardUnstagedChanges",
Other: "discard unstaged changes",
}, &i18n.Message{
ID: "discardAllChangesToAllFiles",
Other: "nuke working tree",
}, &i18n.Message{
ID: "discardAnyUnstagedChanges",
Other: "discard unstaged changes",
}, &i18n.Message{
ID: "discardUntrackedFiles",
Other: "discard untracked files",
}, &i18n.Message{
ID: "hardReset",
Other: "hard reset",
}, &i18n.Message{
ID: "viewResetOptions",
Other: `view reset options`,
}, &i18n.Message{
ID: "createFixupCommit",
Other: `create fixup commit for this commit`,
}, &i18n.Message{
ID: "squashAboveCommits",
Other: `squash above commits`,
}, &i18n.Message{
ID: "SquashAboveCommits",
Other: `Squash above commits`,
}, &i18n.Message{
ID: "SureSquashAboveCommits",
Other: `Are you sure you want to squash all fixup! commits above {{.commit}}?`,
}, &i18n.Message{
ID: "CreateFixupCommit",
Other: `Create fixup commit`,
}, &i18n.Message{
ID: "SureCreateFixupCommit",
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
}, &i18n.Message{
ID: "executeCustomCommand",
Other: "execute custom command",
}, &i18n.Message{
ID: "CustomCommand",
Other: "Custom Command:",
}, &i18n.Message{
ID: "commitChangesWithoutHook",
Other: "commit changes without pre-commit hook",
}, &i18n.Message{
ID: "SkipHookPrefixNotConfigured",
Other: "You have not configured a commit message prefix for skipping hooks. Set `git.skipHookPrefix = 'WIP'` in your config",
}, &i18n.Message{
ID: "resetTo",
Other: `reset to`,
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
}, &i18n.Message{
ID: "viewStashOptions",
Other: "view stash options",
}, &i18n.Message{
ID: "stashAllChanges",
Other: "stash changes",
}, &i18n.Message{
ID: "stashStagedChanges",
Other: "stash staged changes",
}, &i18n.Message{
ID: "stashOptions",
Other: "Stash options",
},
)
}
+128 -23
View File
@@ -26,6 +26,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CommitsTitle",
Other: "Commity",
}, &i18n.Message{
ID: "CommitsDiffTitle",
Other: "Commits (specific diff mode)",
}, &i18n.Message{
ID: "CommitsDiff",
Other: "select commit to diff with another commit",
}, &i18n.Message{
ID: "StashTitle",
Other: "Schowek",
@@ -77,9 +83,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "execute",
Other: "wykonaj",
}, &i18n.Message{
ID: "stashFiles",
Other: "przechowaj pliki",
}, &i18n.Message{
ID: "open",
Other: "otwórz",
@@ -146,9 +149,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "FileNoMergeCons",
Other: "Ten plik nie powoduje konfliktów scalania",
}, &i18n.Message{
ID: "SureResetHardHead",
Other: "Jesteś pewny, że chcesz wykonać `reset --hard HEAD` i `clean -fd`? Możesz stracić wprowadzone zmiany",
}, &i18n.Message{
ID: "SureTo",
Other: "Jesteś pewny, że chcesz {{.deleteVerb}} {{.fileName}} (stracisz swoje wprowadzone zmiany)?",
@@ -332,9 +332,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CantCloseConfirmationPrompt",
Other: "Nie można zamknąć monitu potwierdzenia: {{.error}}",
}, &i18n.Message{
ID: "ClearFilePanel",
Other: "Wyczyść panel plików",
}, &i18n.Message{
ID: "MergeAborted",
Other: "Scalanie anulowane",
@@ -371,9 +368,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &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`,
@@ -386,9 +380,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "refreshFiles",
Other: `odśwież pliki`,
}, &i18n.Message{
ID: "resetHard",
Other: `zresetuj twardo i usuń niepotwierdzone pliki`,
}, &i18n.Message{
ID: "mergeIntoCurrentBranch",
Other: `scal do obecnej gałęzi`,
@@ -466,13 +457,7 @@ func addPolish(i18nObject *i18n.Bundle) error {
Other: "Normal",
}, &i18n.Message{
ID: "softReset",
Other: "soft reset to last commit",
}, &i18n.Message{
ID: "SoftReset",
Other: "Soft reset",
}, &i18n.Message{
ID: "ConfirmSoftReset",
Other: "Are you sure you want to `reset --soft HEAD^`? The changes in your topmost commit will be placed in your working tree",
Other: "soft reset",
}, &i18n.Message{
ID: "SureSquashThisCommit",
Other: "Are you sure you want to squash this commit into the commit below?",
@@ -502,7 +487,7 @@ func addPolish(i18nObject *i18n.Bundle) error {
Other: "amend commit with staged changes",
}, &i18n.Message{
ID: "FoundConflicts",
Other: "Damn, conflicts! To abort press 'esc', otherwise press 'enter'",
Other: "Conflicts! To abort press 'esc', otherwise press 'enter'",
}, &i18n.Message{
ID: "FoundConflictsTitle",
Other: "Auto-merge failed",
@@ -560,6 +545,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CannotRebaseOntoFirstCommit",
Other: "You cannot interactive rebase onto the first commit",
}, &i18n.Message{
ID: "CannotSquashOntoSecondCommit",
Other: "You cannot squash/fixup onto the second commit",
}, &i18n.Message{
ID: "Donate",
Other: "Donate",
@@ -626,6 +614,123 @@ func addPolish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "CherryPickingStatus",
Other: "cherry-picking",
}, &i18n.Message{
ID: "CommitFiles",
Other: "Commit files",
}, &i18n.Message{
ID: "viewCommitFiles",
Other: "view commit's files",
}, &i18n.Message{
ID: "CommitFilesTitle",
Other: "Commit files",
}, &i18n.Message{
ID: "goBack",
Other: "go back",
}, &i18n.Message{
ID: "NoCommiteFiles",
Other: "No files for this commit",
}, &i18n.Message{
ID: "checkoutCommitFile",
Other: "checkout file",
}, &i18n.Message{
ID: "discardOldFileChange",
Other: "discard this commit's changes to this file",
}, &i18n.Message{
ID: "DiscardFileChangesTitle",
Other: "Discard file changes",
}, &i18n.Message{
ID: "DiscardFileChangesPrompt",
Other: "Are you sure you want to discard this commit's changes to this file? If this file was created in this commit, it will be deleted",
}, &i18n.Message{
ID: "DisabledForGPG",
Other: "Feature not available for users using GPG",
}, &i18n.Message{
ID: "CreateRepo",
Other: "Not in a git repository. Create a new git repository? (y/n): ",
}, &i18n.Message{
ID: "AutoStashTitle",
Other: "Autostash?",
}, &i18n.Message{
ID: "AutoStashPrompt",
Other: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
}, &i18n.Message{
ID: "StashPrefix",
Other: "Auto-stashing changes for ",
}, &i18n.Message{
ID: "viewDiscardOptions",
Other: "view 'discard changes' options",
}, &i18n.Message{
ID: "cancel",
Other: "cancel",
}, &i18n.Message{
ID: "discardAllChanges",
Other: "discard all changes",
}, &i18n.Message{
ID: "discardUnstagedChanges",
Other: "discard unstaged changes",
}, &i18n.Message{
ID: "discardAllChangesToAllFiles",
Other: "nuke working tree",
}, &i18n.Message{
ID: "discardAnyUnstagedChanges",
Other: "discard unstaged changes",
}, &i18n.Message{
ID: "discardUntrackedFiles",
Other: "discard untracked files",
}, &i18n.Message{
ID: "hardReset",
Other: "hard reset",
}, &i18n.Message{
ID: "viewResetOptions",
Other: `view reset options`,
}, &i18n.Message{
ID: "createFixupCommit",
Other: `create fixup commit for this commit`,
}, &i18n.Message{
ID: "squashAboveCommits",
Other: `squash above commits`,
}, &i18n.Message{
ID: "SquashAboveCommits",
Other: `Squash above commits`,
}, &i18n.Message{
ID: "SureSquashAboveCommits",
Other: `Are you sure you want to squash all fixup! commits above {{.commit}}?`,
}, &i18n.Message{
ID: "CreateFixupCommit",
Other: `Create fixup commit`,
}, &i18n.Message{
ID: "SureCreateFixupCommit",
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
}, &i18n.Message{
ID: "executeCustomCommand",
Other: "execute custom command",
}, &i18n.Message{
ID: "CustomCommand",
Other: "Custom Command:",
}, &i18n.Message{
ID: "commitChangesWithoutHook",
Other: "commit changes without pre-commit hook",
}, &i18n.Message{
ID: "SkipHookPrefixNotConfigured",
Other: "You have not configured a commit message prefix for skipping hooks. Set `git.skipHookPrefix = 'WIP'` in your config",
}, &i18n.Message{
ID: "resetTo",
Other: `reset to`,
}, &i18n.Message{
ID: "pressEnterToReturn",
Other: "Press enter to return to lazygit",
}, &i18n.Message{
ID: "viewStashOptions",
Other: "view stash options",
}, &i18n.Message{
ID: "stashAllChanges",
Other: "przechowaj pliki",
}, &i18n.Message{
ID: "stashStagedChanges",
Other: "stash staged changes",
}, &i18n.Message{
ID: "stashOptions",
Other: "Stash options",
},
)
}
+3 -2
View File
@@ -33,10 +33,11 @@ func SplitLines(multilineString string) []string {
// WithPadding pads a string as much as you want
func WithPadding(str string, padding int) string {
if padding-len(str) < 0 {
uncoloredStr := Decolorise(str)
if padding < len(uncoloredStr) {
return str
}
return str + strings.Repeat(" ", padding-len(str))
return str + strings.Repeat(" ", padding-len(uncoloredStr))
}
// ColoredString takes a string and a colour attribute and returns a colored
+24 -8
View File
@@ -25,16 +25,21 @@ type bindingSection struct {
}
func main() {
langs := []string{"pl", "nl", "en"}
mConfig, _ := config.NewAppConfig("", "", "", "", "", true)
mApp, _ := app.NewApp(mConfig)
lang := mApp.Tr.GetLanguage()
file, _ := os.Create("Keybindings_" + lang + ".md")
bindingSections := getBindingSections(mApp)
for _, lang := range langs {
os.Setenv("LC_ALL", lang)
mApp, _ := app.NewApp(mConfig)
file, err := os.Create(getProjectRoot() + "/docs/keybindings/Keybindings_" + lang + ".md")
if err != nil {
panic(err)
}
content := formatSections(mApp, bindingSections)
writeString(file, content)
bindingSections := getBindingSections(mApp)
content := formatSections(mApp, bindingSections)
writeString(file, content)
}
}
func writeString(file *os.File, str string) {
@@ -54,6 +59,9 @@ func formatTitle(title string) string {
}
func formatBinding(binding *gui.Binding) string {
if binding.Alternative != "" {
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
}
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
}
@@ -91,7 +99,7 @@ func getBindingSections(mApp *app.App) []*bindingSection {
}
func addBinding(title string, bindingSections []*bindingSection, binding *gui.Binding) []*bindingSection {
if binding.Description == "" {
if binding.Description == "" && binding.Alternative == "" {
return bindingSections
}
@@ -124,3 +132,11 @@ func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
return content
}
func getProjectRoot() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return strings.Split(dir, "lazygit")[0] + "lazygit"
}
+1 -1
View File
@@ -10,7 +10,7 @@ fi
for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" ! -path "./scripts*" -type d); do
if ls $d/*.go &> /dev/null; then
args="-v -race -coverprofile=profile.out -covermode=atomic $d"
args="-race -coverprofile=profile.out -covermode=atomic $d"
if [ "$use_go_test" == true ]; then
gotest $args
else
+20 -1
View File
@@ -133,4 +133,23 @@ echo "once upon a time there was a horse" >> file5
git add file5
git commit -m "fourth commit on master"
# git merge develop # should have a merge conflict here
# this is for the autostash feature
git checkout -b base_branch
echo "original1\noriginal2\noriginal3" > file
git add file
git commit -m "file"
git checkout -b other_branch
git checkout base_branch
echo "new1\noriginal2\noriginal3" > file
git add file
git commit -m "file changed"
git checkout other_branch
echo "new2\noriginal2\noriginal3" > file
+56 -8
View File
@@ -33,18 +33,10 @@ var DefaultEditor Editor = EditorFunc(simpleEditor)
// simpleEditor is used as the default gocui editor.
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
switch {
case ch != 0 && mod == 0:
v.EditWrite(ch)
case key == KeySpace:
v.EditWrite(' ')
case key == KeyBackspace || key == KeyBackspace2:
v.EditDelete(true)
case key == KeyDelete:
v.EditDelete(false)
case key == KeyInsert:
v.Overwrite = !v.Overwrite
case key == KeyEnter:
v.EditNewLine()
case key == KeyArrowDown:
v.MoveCursor(0, 1, false)
case key == KeyArrowUp:
@@ -53,6 +45,20 @@ func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
v.MoveCursor(-1, 0, false)
case key == KeyArrowRight:
v.MoveCursor(1, 0, false)
case key == KeyTab:
v.EditNewLine()
case key == KeySpace:
v.EditWrite(' ')
case key == KeyInsert:
v.Overwrite = !v.Overwrite
case key == KeyCtrlU:
v.EditDeleteToStartOfLine()
case key == KeyCtrlA:
v.EditGotoToStartOfLine()
case key == KeyCtrlE:
v.EditGotoToEndOfLine()
default:
v.EditWrite(ch)
}
}
@@ -63,6 +69,48 @@ func (v *View) EditWrite(ch rune) {
v.moveCursor(w, 0, true)
}
// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the end of the line. Or if you are already at the start of the line, it deletes the newline character
func (v *View) EditDeleteToStartOfLine() {
x, _ := v.Cursor()
if x == 0 {
v.EditDelete(true)
} else {
// delete characters until we are the start of the line
for x > 0 {
v.EditDelete(true)
x, _ = v.Cursor()
}
}
}
// EditGotoToStartOfLine takes you to the start of the current line
func (v *View) EditGotoToStartOfLine() {
x, _ := v.Cursor()
for x > 0 {
v.MoveCursor(-1, 0, false)
x, _ = v.Cursor()
}
}
// EditGotoToEndOfLine takes you to the end of the line
func (v *View) EditGotoToEndOfLine() {
_, y := v.Cursor()
_ = v.SetCursor(0, y+1)
x, newY := v.Cursor()
if newY == y {
// we must be on the last line, so lets move to the very end
prevX := -1
for prevX != x {
prevX = x
v.MoveCursor(1, 0, false)
x, _ = v.Cursor()
}
} else {
// most left so now we're at the end of the original line
v.MoveCursor(-1, 0, false)
}
}
// EditDelete deletes a rune at the cursor position. back determines the
// direction.
func (v *View) EditDelete(back bool) {
+31 -2
View File
@@ -151,7 +151,7 @@ func (g *Gui) Rune(x, y int) (rune, error) {
// ErrUnknownView is returned, which allows to assert if the View must
// be initialized. It checks if the position is valid.
func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, error) {
if x0 >= x1 || y0 >= y1 {
if x0 >= x1 {
return nil, errors.New("invalid dimensions")
}
if name == "" {
@@ -175,6 +175,17 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int, overlaps byte) (*View, er
return v, errors.Wrap(ErrUnknownView, 0)
}
// SetViewBeneath sets a view stacked beneath another view
func (g *Gui) SetViewBeneath(name string, aboveViewName string, height int) (*View, error) {
aboveView, err := g.View(aboveViewName)
if err != nil {
return nil, err
}
viewTop := aboveView.y1 + 1
return g.SetView(name, aboveView.x0, viewTop, aboveView.x1, viewTop+height-1, 0)
}
// SetViewOnTop sets the given view on top of the existing ones.
func (g *Gui) SetViewOnTop(name string) (*View, error) {
for i, v := range g.views {
@@ -471,6 +482,9 @@ func (g *Gui) flush() error {
}
}
for _, v := range g.views {
if v.y1 < v.y0 {
continue
}
if v.Frame {
var fgColor, bgColor Attribute
if g.Highlight && v == g.currentView {
@@ -557,6 +571,18 @@ func corner(v *View, directions byte) rune {
// drawFrameCorners draws the corners of the view.
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
if v.y0 == v.y1 {
if !g.SupportOverlaps && v.x0 >= 0 && v.x1 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.x1 < g.maxX && v.y0 < g.maxY {
if err := g.SetRune(v.x0, v.y0, '╶', fgColor, bgColor); err != nil {
return err
}
if err := g.SetRune(v.x1, v.y0, '╴', fgColor, bgColor); err != nil {
return err
}
}
return nil
}
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
if g.SupportOverlaps {
runeTL = corner(v, BOTTOM|RIGHT)
@@ -610,6 +636,9 @@ func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error {
}
start := v.x1 - 5 - len(v.Subtitle)
if start < v.x0 {
return nil
}
for i, ch := range v.Subtitle {
x := start + i
if x >= v.x1 {
@@ -704,7 +733,7 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err
if kb.matchView(v) {
return g.execKeybinding(v, kb)
}
if kb.viewName == "" && (!v.Editable || kb.ch == 0) {
if kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) {
globalKb = kb
}
}
+4 -1
View File
@@ -35,10 +35,13 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
// if the user is typing in a field, ignore char keys
if v == nil {
return false
}
if v.Editable == true && kb.ch != 0 {
return false
}
return v != nil && kb.viewName == v.name
return kb.viewName == v.name
}
// Key represents special keys or keys combinations.
+11
View File
@@ -8,6 +8,7 @@ import (
"bytes"
"io"
"strings"
"sync"
"time"
"github.com/go-errors/errors"
@@ -91,6 +92,8 @@ type View struct {
// If HasLoader is true, the message will be appended with a spinning loader animation
HasLoader bool
writeMutex sync.Mutex
}
type viewLine struct {
@@ -224,6 +227,8 @@ func (v *View) Origin() (x, y int) {
// be called to clear the view's buffer.
func (v *View) Write(p []byte) (n int, err error) {
v.tainted = true
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
for _, ch := range bytes.Runes(p) {
switch ch {
@@ -250,6 +255,7 @@ func (v *View) Write(p []byte) (n int, err error) {
}
}
}
return len(p), nil
}
@@ -603,3 +609,8 @@ func Loader() cell {
chr: chr,
}
}
// IsTainted tells us if the view is tainted
func (v *View) IsTainted() bool {
return v.tainted
}