Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6cb90e8ca | ||
|
|
fb156bcaac | ||
|
|
75ba2196ba | ||
|
|
4cb50b15e4 | ||
|
|
ca5cbe4d44 | ||
|
|
df050472a1 | ||
|
|
c173ebf5b9 | ||
|
|
434582b5f5 | ||
|
|
cf6be928a3 | ||
|
|
c907c55144 | ||
|
|
ee433ab909 | ||
|
|
bf69923b6d | ||
|
|
64782a433e | ||
|
|
44edb49a6e | ||
|
|
1a6d269063 | ||
|
|
b64953ebdb | ||
|
|
deaa2bcb15 | ||
|
|
c166c57c5d | ||
|
|
6b77e4ee4a | ||
|
|
e5534f060d | ||
|
|
466e0be560 | ||
|
|
810adab957 | ||
|
|
83a3c9fc8d | ||
|
|
5e95019b3f | ||
|
|
8e7f278094 | ||
|
|
83a895a463 | ||
|
|
59ae1e1599 | ||
|
|
77a82e9d51 | ||
|
|
bd79c2e8dc | ||
|
|
23bcc19180 | ||
|
|
282f08df36 | ||
|
|
d647a96ed5 | ||
|
|
1b64ea3210 | ||
|
|
9b32e99eb8 | ||
|
|
79e696d8a7 | ||
|
|
a3ea19be8e | ||
|
|
e0015a52e5 | ||
|
|
aea4661be5 | ||
|
|
80377e4716 | ||
|
|
c3d54f3c2e | ||
|
|
c7d367a791 | ||
|
|
ba4253668d | ||
|
|
1ce5c69cd2 | ||
|
|
205d731d7b | ||
|
|
3e875cc593 | ||
|
|
de2cfc7e17 | ||
|
|
e72cab81c1 | ||
|
|
844a2db83a | ||
|
|
529ba45cc7 | ||
|
|
09aabce3cd | ||
|
|
eb2bfd3848 | ||
|
|
adb5c8fe06 | ||
|
|
d914d40b2e | ||
|
|
66c7672a0c | ||
|
|
983379d334 | ||
|
|
fd72a09d1e | ||
|
|
0ddf7c05c8 | ||
|
|
818134247d | ||
|
|
ed020e18a0 | ||
|
|
9e5acb84c0 | ||
|
|
5e45ae1584 | ||
|
|
86b101c410 | ||
|
|
96ca7262e4 | ||
|
|
0a31edecb6 | ||
|
|
6ac6d142c0 | ||
|
|
5bb9900220 | ||
|
|
f99a200db0 | ||
|
|
24e73cdd8b | ||
|
|
be8f589c32 | ||
|
|
3074ae99ea | ||
|
|
873fe41ab3 | ||
|
|
029de4ac86 | ||
|
|
5f21f190b9 | ||
|
|
dab78c8a63 | ||
|
|
0d1230a959 | ||
|
|
c507e5f562 | ||
|
|
63e353ad6a | ||
|
|
7194dfa43c | ||
|
|
e425f1df87 | ||
|
|
3f4613feb0 | ||
|
|
033c21754b | ||
|
|
c89c35c6b3 | ||
|
|
1dbfea54bc | ||
|
|
0af8784707 | ||
|
|
2ca5766f56 | ||
|
|
a0b842204c | ||
|
|
0a26050b47 | ||
|
|
c50ab9872d | ||
|
|
fa6893fda9 | ||
|
|
710abded64 | ||
|
|
1c38db1fc7 | ||
|
|
339e1b5dcf | ||
|
|
7113ed73d4 | ||
|
|
3dd1daacdc | ||
|
|
bad06bb634 | ||
|
|
e18e81f5eb | ||
|
|
67a446234c | ||
|
|
f905b27b00 | ||
|
|
e36ee0b4f1 | ||
|
|
3c13229145 | ||
|
|
cea24c2cf9 | ||
|
|
64017cf874 | ||
|
|
b3bce8a1ba | ||
|
|
3b0cef2ec8 | ||
|
|
07cbae4019 | ||
|
|
b42202ea1c | ||
|
|
8347dcd671 | ||
|
|
dcb5285797 | ||
|
|
a9cd647075 | ||
|
|
f0cd730fbb | ||
|
|
2afbd7ba7f | ||
|
|
55ff0c0dee | ||
|
|
6b7aaeca45 | ||
|
|
1f3e1720a3 | ||
|
|
b7f2d0366b | ||
|
|
6bd0979b4a | ||
|
|
986abc1e45 | ||
|
|
61dac10bb9 | ||
|
|
b5385f2560 | ||
|
|
92e43d9e77 | ||
|
|
325408d0e3 | ||
|
|
eeb667954f | ||
|
|
8aa1062e06 | ||
|
|
7e0a8f235e | ||
|
|
44bbc106a9 | ||
|
|
e6be849eb2 | ||
|
|
092f27495a | ||
|
|
7849f91d80 | ||
|
|
5f769da74d | ||
|
|
963c034b48 | ||
|
|
f15e47bb67 | ||
|
|
7995d56a85 |
@@ -17,16 +17,16 @@ builds:
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
|
||||
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: 32-bit
|
||||
amd64: x86_64
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: 32-bit
|
||||
amd64: x86_64
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
||||
32
README.md
32
README.md
@@ -37,13 +37,16 @@ Core:
|
||||
brew install lazygit
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
Latest version built from github releases.
|
||||
Tap:
|
||||
```
|
||||
sudo port install lazygit
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
Packages for Ubuntu 16.04, 18.04 and 18.10 are available via [Launchpad PPA](https://launchpad.net/~lazygit-team).
|
||||
|
||||
**Release builds**
|
||||
|
||||
Built from git tags. Supposed to be more stable.
|
||||
Packages for Ubuntu are available via [Launchpad PPA](https://launchpad.net/~lazygit-team).
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:lazygit-team/release
|
||||
@@ -51,16 +54,6 @@ sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
**Daily builds**
|
||||
|
||||
Built from master branch once in 24 hours (or more sometimes).
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:lazygit-team/daily
|
||||
sudo apt-get update
|
||||
sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
### Void Linux
|
||||
|
||||
Packages for Void Linux are available in the distro repo
|
||||
@@ -84,6 +77,15 @@ and the git version which builds from the most recent commit.
|
||||
Instruction of how to install AUR content can be found here:
|
||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
|
||||
### Fedora and CentOS 7
|
||||
|
||||
Packages for Fedora and CentOS 7 are available via [Copr](https://copr.fedorainfracloud.org/coprs/atim/lazygit/) (Cool Other Package Repo).
|
||||
|
||||
```sh
|
||||
sudo dnf copr enable atim/lazygit -y
|
||||
sudo dnf install lazygit
|
||||
```
|
||||
|
||||
### Conda
|
||||
|
||||
Released versions are available for different platforms, see https://anaconda.org/conda-forge/lazygit
|
||||
|
||||
125
docs/Config.md
125
docs/Config.md
@@ -1,5 +1,7 @@
|
||||
# User Config:
|
||||
|
||||
Default path for the config file: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
|
||||
## Default:
|
||||
|
||||
```yaml
|
||||
@@ -19,6 +21,7 @@
|
||||
commitLength:
|
||||
show: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
git:
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
@@ -30,6 +33,98 @@
|
||||
days: 14 # how often an update is checked for
|
||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||
confirmOnQuit: false
|
||||
keybinding:
|
||||
universal:
|
||||
quit: 'q'
|
||||
quit-alt1: '<c-c>' # alternative/alias of quit
|
||||
return: '<esc>' # return to previous menu, will quit if there's nowhere to return
|
||||
quitWithoutChangingDirectory: 'Q'
|
||||
togglePanel: '<tab>' # goto the next panel
|
||||
prevItem: '<up>' # go one line up
|
||||
nextItem: '<down>' # go one line down
|
||||
prevItem-alt: 'k' # go one line up
|
||||
nextItem-alt: 'j' # go one line down
|
||||
prevBlock: '<left>' # goto the previous block / panel
|
||||
nextBlock: '<right>' # goto the next block / panel
|
||||
prevBlock-alt: 'h' # goto the previous block / panel
|
||||
nextBlock-alt: 'l' # goto the next block / panel
|
||||
optionMenu: 'x' # show help menu
|
||||
optionMenu-alt1: '?' # show help menu
|
||||
select: '<space>'
|
||||
goInto: '<enter>'
|
||||
remove: 'd'
|
||||
new: 'n'
|
||||
edit: 'e'
|
||||
openFile: 'o'
|
||||
scrollUpMain: '<pgup>' # main panel scrool up
|
||||
scrollDownMain: '<pgdown>' # main panel scrool down
|
||||
scrollUpMain-alt1: 'K' # main panel scrool up
|
||||
scrollDownMain-alt1: 'J' # main panel scrool down
|
||||
scrollUpMain-alt2: '<c-u>' # main panel scrool up
|
||||
scrollDownMain-alt2: '<c-d>' # main panel scrool down
|
||||
executeCustomCommand: 'X'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
refresh: 'R'
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
files:
|
||||
commitChanges: 'c'
|
||||
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
|
||||
amendLastCommit: 'A'
|
||||
commitChangesWithEditor: 'C'
|
||||
ignoreFile: 'i'
|
||||
refreshFiles: 'r'
|
||||
stashAllChanges: 's'
|
||||
viewStashOptions: 'S'
|
||||
toggleStagedAll: 'a' # stage/unstage all
|
||||
viewResetOptions: 'D'
|
||||
fetch: 'f'
|
||||
branches:
|
||||
createPullRequest: 'o'
|
||||
checkoutBranchByName: 'c'
|
||||
forceCheckoutBranch: 'F'
|
||||
rebaseBranch: 'r'
|
||||
mergeIntoCurrentBranch: 'M'
|
||||
viewGitFlowOptions: 'i'
|
||||
fastForward: 'f' # fast-forward this branch from its upstream
|
||||
pushTag: 'P'
|
||||
setUpstream: 'u' # set as upstream of checked-out branch
|
||||
fetchRemote: 'f'
|
||||
commits:
|
||||
squashDown: 's'
|
||||
renameCommit: 'r'
|
||||
renameCommitWithEditor: 'R'
|
||||
viewResetOptions: 'g'
|
||||
markCommitAsFixup: 'f'
|
||||
createFixupCommit: 'F' # create fixup commit for this commit
|
||||
squashAboveCommits: 'S'
|
||||
moveDownCommit: '<c-j>' # move commit down one
|
||||
moveUpCommit: '<c-k>' # move commit up one
|
||||
amendToCommit: 'A'
|
||||
pickCommit: 'p' # pick commit (when mid-rebase)
|
||||
revertCommit: 't'
|
||||
cherryPickCopy: 'c'
|
||||
cherryPickCopyRange: 'C'
|
||||
pasteCommits: 'v'
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
checkoutCommitFile: 'c'
|
||||
main:
|
||||
toggleDragSelect: 'v'
|
||||
toggleDragSelect-alt: 'V'
|
||||
toggleSelectHunk: 'a'
|
||||
pickBothHunks: 'b'
|
||||
undo: 'z'
|
||||
```
|
||||
|
||||
## Platform Defaults:
|
||||
@@ -100,3 +195,33 @@ If you have issues with a light terminal theme where you can't read / see the te
|
||||
## Example Coloring:
|
||||
|
||||

|
||||
|
||||
## Keybindings:
|
||||
For all possible keybinding options, check [Custom_Keybinding.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybinding.md)
|
||||
|
||||
|
||||
#### Example Keybindings For Colemak Users:
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
prevItem-alt: 'u'
|
||||
nextItem-alt: 'e'
|
||||
prevBlock-alt: 'n'
|
||||
nextBlock-alt: 'i'
|
||||
new: 'k'
|
||||
edit: 'o'
|
||||
openFile: 'O'
|
||||
scrollUpMain-alt1: 'U'
|
||||
scrollDownMain-alt1: 'E'
|
||||
scrollUpMain-alt2: '<c-u>'
|
||||
scrollDownMain-alt2: '<c-e>'
|
||||
files:
|
||||
ignoreFile: 'I'
|
||||
commits:
|
||||
moveDownCommit: '<c-e>'
|
||||
moveUpCommit: '<c-u>'
|
||||
toggleDiffCommit: 'l'
|
||||
branches:
|
||||
viewGitFlowOptions: 'I'
|
||||
```
|
||||
|
||||
|
||||
59
docs/keybindings/Custom_Keybindings.md
Normal file
59
docs/keybindings/Custom_Keybindings.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## Possible keybindings
|
||||
| Put in | You will get |
|
||||
|---------------|----------------|
|
||||
| `<f1>` | F1 |
|
||||
| `<f2>` | F2 |
|
||||
| `<f3>` | F3 |
|
||||
| `<f4>` | F4 |
|
||||
| `<f5>` | F5 |
|
||||
| `<f6>` | F6 |
|
||||
| `<f7>` | F7 |
|
||||
| `<f8>` | F8 |
|
||||
| `<f9>` | F9 |
|
||||
| `<f10>` | F10 |
|
||||
| `<f11>` | F11 |
|
||||
| `<f12>` | F12 |
|
||||
| `<insert>` | Insert |
|
||||
| `<delete>` | Delete |
|
||||
| `<home>` | Home |
|
||||
| `<end>` | End |
|
||||
| `<pgup>` | Pgup |
|
||||
| `<pgdown>` | Pgdn |
|
||||
| `<up>` | ArrowUp |
|
||||
| `<down>` | ArrowDown |
|
||||
| `<left>` | ArrowLeft |
|
||||
| `<right>` | ArrowRight |
|
||||
| `<tab>` | Tab |
|
||||
| `<enter>` | Enter |
|
||||
| `<esc>` | Esc |
|
||||
| `<backspace>` | Backspace |
|
||||
| `<c-space>` | CtrlSpace |
|
||||
| `<c-/>` | CtrlSlash |
|
||||
| `<space>` | Space |
|
||||
| `<c-a>` | CtrlA |
|
||||
| `<c-b>` | CtrlB |
|
||||
| `<c-c>` | CtrlC |
|
||||
| `<c-d>` | CtrlD |
|
||||
| `<c-e>` | CtrlE |
|
||||
| `<c-f>` | CtrlF |
|
||||
| `<c-g>` | CtrlG |
|
||||
| `<c-j>` | CtrlJ |
|
||||
| `<c-k>` | CtrlK |
|
||||
| `<c-l>` | CtrlL |
|
||||
| `<c-n>` | CtrlN |
|
||||
| `<c-o>` | CtrlO |
|
||||
| `<c-p>` | CtrlP |
|
||||
| `<c-q>` | CtrlQ |
|
||||
| `<c-r>` | CtrlR |
|
||||
| `<c-s>` | CtrlS |
|
||||
| `<c-t>` | CtrlT |
|
||||
| `<c-u>` | CtrlU |
|
||||
| `<c-v>` | CtrlV |
|
||||
| `<c-w>` | CtrlW |
|
||||
| `<c-x>` | CtrlX |
|
||||
| `<c-y>` | CtrlY |
|
||||
| `<c-z>` | CtrlZ |
|
||||
| `<c-4>` | Ctrl4 |
|
||||
| `<c-5>` | Ctrl5 |
|
||||
| `<c-6>` | Ctrl6 |
|
||||
| `<c-8>` | Ctrl8 |
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<pre>
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: refresh
|
||||
|
||||
34
go.mod
34
go.mod
@@ -4,39 +4,41 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.9 // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/integrii/flaggy v1.3.0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5
|
||||
github.com/jesseduffield/pty v1.2.1
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e // indirect
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.8
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.3
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/viper v1.5.0
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.6.1
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/tcnksm/go-gitconfig v0.1.2
|
||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 // indirect
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/tools v0.0.0-20191113055240-e33b02e76616 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v2 v2.2.5
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
)
|
||||
|
||||
84
go.sum
84
go.sum
@@ -24,7 +24,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -64,6 +63,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
@@ -72,14 +73,20 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/integrii/flaggy v1.3.0 h1:8I5Qqz22C6+EwUqJuaN5ITh77obI8VSg6RwYLe2VB7o=
|
||||
github.com/integrii/flaggy v1.3.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||
github.com/integrii/flaggy v1.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM=
|
||||
github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||
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/gocui v0.3.1-0.20191110053728-01cdcccd0508 h1:8CPQLUe+0QXxnbnUfaMxh1UGxg3rYCqCvbxecC9rrIY=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/pty v1.1.3 h1:JTPQ/bbGH3IBwQhxKZiFxfD4wv7cYrY7LNTwbucsZ64=
|
||||
github.com/jesseduffield/pty v1.1.3/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532 h1:V1Lk2rm5/p27NjnlF2ezzkxDaisHNcveMNueSD7RYgs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351 h1:+sSqd2YotacWt+1MNRN8ZmXnYoiJeblZeptzKiHIyv0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039 h1:CVhilJ8ZdN7GmAI+fbH9829Cp/8hbK7Lijbd4VaNgo0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac h1:vp7I0RpFq4L46nFA9QQokzhFgr68LRGtwDO9xfq4F+A=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5 h1:tE0w3tuL/bj1o5VMhjjE0ep6i7Fva+RYjKcMFcniJEY=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||
@@ -88,8 +95,12 @@ github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 h1:CRD7bVjlG
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7/go.mod h1:VspA3aTkEo0Q7TPCLmX1uHNP+Wb4iSDX09hmTRo1QYc=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e h1:tth7wr6+sfSbdpRWWrwvLYyS56HyIRVfq0Qcl2h28wM=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 h1:iBBk1lhFwjwJw//J2m1yyz9S368GeXQTpMVACTyQMh0=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
@@ -99,6 +110,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -111,10 +124,12 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
@@ -123,8 +138,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.3 h1:ks/JkQiOEhhuF6jpNvx+Wih1NIiXzUnZeZVnJuI8R8M=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
@@ -134,6 +149,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
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/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -157,18 +174,28 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
|
||||
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
|
||||
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
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.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
@@ -200,8 +227,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
|
||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -215,14 +242,13 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -236,10 +262,9 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea h1:Mz1TMnfJDRJLk8S8OPCoJYgrsp/Se/2TBre2+vwX128=
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -248,11 +273,9 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191113055240-e33b02e76616/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@@ -261,9 +284,12 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
@@ -279,6 +305,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/rollrus"
|
||||
"github.com/shibukawa/configdir"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -73,9 +72,7 @@ func newDevelopmentLogger(config config.AppConfigurer) *logrus.Logger {
|
||||
|
||||
func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
var log *logrus.Logger
|
||||
environment := "production"
|
||||
if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" {
|
||||
environment = "development"
|
||||
log = newDevelopmentLogger(config)
|
||||
} else {
|
||||
log = newProductionLogger(config)
|
||||
@@ -85,11 +82,6 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
// https://github.com/aybabtme/humanlog
|
||||
log.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
if 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)
|
||||
}
|
||||
return log.WithFields(logrus.Fields{
|
||||
"debug": config.GetDebug(),
|
||||
"version": config.GetVersion(),
|
||||
|
||||
@@ -22,7 +22,7 @@ type Branch struct {
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, b.GetColor())
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
}
|
||||
@@ -30,9 +30,11 @@ func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{b.Recency, displayName}
|
||||
}
|
||||
|
||||
// GetColor branch color
|
||||
func (b *Branch) GetColor() color.Attribute {
|
||||
switch b.getType() {
|
||||
// GetBranchColor branch color
|
||||
func GetBranchColor(name string) color.Attribute {
|
||||
branchType := strings.Split(name, "/")[0]
|
||||
|
||||
switch branchType {
|
||||
case "feature":
|
||||
return color.FgGreen
|
||||
case "bugfix":
|
||||
@@ -43,8 +45,3 @@ func (b *Branch) GetColor() color.Attribute {
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
|
||||
// expected to return feature/bugfix/hotfix or blank string
|
||||
func (b *Branch) getType() string {
|
||||
return strings.Split(b.Name, "/")[0]
|
||||
}
|
||||
|
||||
@@ -47,16 +47,20 @@ func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
||||
|
||||
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
|
||||
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||
unescaped := "git reflog --date=relative --pretty='%gd|%gs' --grep-reflog='checkout: moving' HEAD"
|
||||
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput(unescaped)
|
||||
if err != nil {
|
||||
return branches
|
||||
}
|
||||
|
||||
branchLines := utils.SplitLines(rawString)
|
||||
for _, line := range branchLines {
|
||||
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
|
||||
timeUnit = abbreviatedTimeUnit(timeUnit)
|
||||
branch := &Branch{Name: branchName, Recency: timeNumber + timeUnit}
|
||||
recency, branchName := branchInfoFromLine(line)
|
||||
if branchName == "" {
|
||||
continue
|
||||
}
|
||||
branch := &Branch{Name: branchName, Recency: recency}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return uniqueByName(branches)
|
||||
@@ -141,11 +145,17 @@ func uniqueByName(branches []*Branch) []*Branch {
|
||||
|
||||
// A line will have the form '10 days ago master' so we need to strip out the
|
||||
// useful information from that into timeNumber, timeUnit, and branchName
|
||||
func branchInfoFromLine(line string) (string, string, string) {
|
||||
r := regexp.MustCompile("\\|.*\\s")
|
||||
line = r.ReplaceAllString(line, " ")
|
||||
words := strings.Split(line, " ")
|
||||
return words[0], words[1], words[len(words)-1]
|
||||
func branchInfoFromLine(line string) (string, string) {
|
||||
// example line: HEAD@{12 minutes ago}|checkout: moving from pulling-from-forks to tim77-patch-1
|
||||
r := regexp.MustCompile(`HEAD\@\{([^\s]+) ([^\s]+) ago\}\|.*?([^\s]*)$`)
|
||||
matches := r.FindStringSubmatch(strings.TrimSpace(line))
|
||||
if len(matches) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
since := matches[1]
|
||||
unit := matches[2]
|
||||
branchName := matches[3]
|
||||
return since + abbreviatedTimeUnit(unit), branchName
|
||||
}
|
||||
|
||||
func abbreviatedTimeUnit(timeUnit string) string {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -14,6 +16,7 @@ type Commit struct {
|
||||
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
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
@@ -41,6 +44,8 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
@@ -52,9 +57,12 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
tagString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
} else if len(c.Tags) > 0 {
|
||||
tagString = utils.ColoredString(strings.Join(c.Tags, " "), color.FgMagenta) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha), actionString + defaultColor.Sprint(c.Name)}
|
||||
return []string{shaColor.Sprint(c.Sha), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
||||
func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
var rebasingCommits []*Commit
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
@@ -65,7 +65,7 @@ func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits()
|
||||
log := c.getLog()
|
||||
log := c.getLog(limit)
|
||||
|
||||
// now we can split it up and turn it into commits
|
||||
for _, line := range utils.SplitLines(log) {
|
||||
@@ -78,6 +78,7 @@ func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
||||
Name: strings.Join(splitLine[1:], " "),
|
||||
Status: status,
|
||||
DisplayString: strings.Join(splitLine, " "),
|
||||
// TODO: add tags here
|
||||
})
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
@@ -261,7 +262,7 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
}
|
||||
|
||||
// swallowing error because it's not a big deal; probably because there are no commits yet
|
||||
output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
|
||||
output, _ := c.OSCommand.RunCommandWithOutput("git merge-base HEAD %s", baseBranch)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -280,12 +281,15 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
return pushables
|
||||
}
|
||||
|
||||
// getLog gets the git log (currently limited to 30 commits for performance
|
||||
// until we work out lazy loading
|
||||
func (c *CommitListBuilder) getLog() string {
|
||||
// currently limiting to 30 for performance reasons
|
||||
// TODO: add lazyloading when you scroll down
|
||||
result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30")
|
||||
// getLog gets the git log.
|
||||
func (c *CommitListBuilder) getLog(limit bool) string {
|
||||
limitFlag := ""
|
||||
if limit {
|
||||
limitFlag = "-30"
|
||||
}
|
||||
|
||||
c.Log.Warn(fmt.Sprintf("git log --oneline %s", limitFlag))
|
||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline %s", limitFlag))
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestCommitListBuilderGetLog(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getLog())
|
||||
s.test(c.getLog(true))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -289,6 +289,10 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
// here's where we are returning the error
|
||||
return exec.Command("test")
|
||||
case "branch":
|
||||
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||
// here too
|
||||
return exec.Command("test")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
// here too
|
||||
@@ -308,7 +312,7 @@ func TestCommitListBuilderGetCommits(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.GetCommits())
|
||||
s.test(c.GetCommits(true))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -21,7 +22,13 @@ import (
|
||||
gogit "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
func verifyInGitRepo(runCmd func(string) error) error {
|
||||
// this takes something like:
|
||||
// * (HEAD detached at 264fc6f5)
|
||||
// remotes
|
||||
// and returns '264fc6f5' as the second match
|
||||
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
|
||||
|
||||
func verifyInGitRepo(runCmd func(string, ...interface{}) error) error {
|
||||
return runCmd("git status")
|
||||
}
|
||||
|
||||
@@ -150,7 +157,9 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
|
||||
|
||||
// GetStashEntries stash entries
|
||||
func (c *GitCommand) GetStashEntries() []*StashEntry {
|
||||
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
|
||||
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||
unescaped := "git stash list --pretty='%gs'"
|
||||
rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped)
|
||||
stashEntries := []*StashEntry{}
|
||||
for i, line := range utils.SplitLines(rawString) {
|
||||
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
|
||||
@@ -167,8 +176,8 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
||||
}
|
||||
|
||||
// GetStashEntryDiff stash diff
|
||||
func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{" + fmt.Sprint(index) + "}")
|
||||
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||
return fmt.Sprintf("git stash show -p --color stash@{%d}", index)
|
||||
}
|
||||
|
||||
// GetStatusFiles git status files
|
||||
@@ -182,10 +191,10 @@ func (c *GitCommand) GetStatusFiles() []*File {
|
||||
stagedChange := change[0:1]
|
||||
unstagedChange := statusString[1:2]
|
||||
filename := c.OSCommand.Unquote(statusString[3:])
|
||||
_, untracked := map[string]bool{"??": true, "A ": true, "AM": true}[change]
|
||||
_, hasNoStagedChanges := map[string]bool{" ": true, "U": true, "?": true}[stagedChange]
|
||||
hasMergeConflicts := change == "UU" || change == "AA" || change == "DU"
|
||||
hasInlineMergeConflicts := change == "UU" || change == "AA"
|
||||
untracked := utils.IncludesString([]string{"??", "A ", "AM"}, change)
|
||||
hasNoStagedChanges := utils.IncludesString([]string{" ", "U", "?"}, stagedChange)
|
||||
hasMergeConflicts := utils.IncludesString([]string{"DD", "AA", "UU", "AU", "UA", "UD", "DU"}, change)
|
||||
hasInlineMergeConflicts := utils.IncludesString([]string{"UU", "AA"}, change)
|
||||
|
||||
file := &File{
|
||||
Name: filename,
|
||||
@@ -206,13 +215,13 @@ func (c *GitCommand) GetStatusFiles() []*File {
|
||||
|
||||
// StashDo modify stash
|
||||
func (c *GitCommand) StashDo(index int, method string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash %s stash@{%d}", method, index))
|
||||
return c.OSCommand.RunCommand("git stash %s stash@{%d}", method, index)
|
||||
}
|
||||
|
||||
// StashSave save stash
|
||||
// TODO: before calling this, check if there is anything to save
|
||||
func (c *GitCommand) StashSave(message string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash save %s", c.OSCommand.Quote(message)))
|
||||
return c.OSCommand.RunCommand("git stash save %s", c.OSCommand.Quote(message))
|
||||
}
|
||||
|
||||
// MergeStatusFiles merge status files
|
||||
@@ -256,7 +265,7 @@ 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.ResetHardHead(); err != nil {
|
||||
if err := c.ResetHard("HEAD"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -264,23 +273,22 @@ func (c *GitCommand) ResetAndClean() error {
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
||||
return c.GetCommitDifferences("HEAD", "@{u}")
|
||||
return c.GetCommitDifferences("HEAD", "HEAD@{u}")
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
|
||||
upstream := "origin" // hardcoded for now
|
||||
return c.GetCommitDifferences(branchName, fmt.Sprintf("%s/%s", upstream, branchName))
|
||||
return c.GetCommitDifferences(branchName, branchName+"@{u}")
|
||||
}
|
||||
|
||||
// GetCommitDifferences checks how many pushables/pullables there are for the
|
||||
// current branch
|
||||
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
|
||||
command := "git rev-list %s..%s --count"
|
||||
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
|
||||
pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from)
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
pullableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, from, to))
|
||||
pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to)
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
@@ -289,7 +297,7 @@ func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
|
||||
|
||||
// RenameCommit renames the topmost commit with the given name
|
||||
func (c *GitCommand) RenameCommit(name string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name)))
|
||||
return c.OSCommand.RunCommand("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name))
|
||||
}
|
||||
|
||||
// RebaseBranch interactive rebases onto a branch
|
||||
@@ -314,22 +322,25 @@ func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCrede
|
||||
|
||||
// ResetToCommit reset to commit
|
||||
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git reset --%s %s", strength, sha))
|
||||
return c.OSCommand.RunCommand("git reset --%s %s", strength, sha)
|
||||
}
|
||||
|
||||
// NewBranch create new branch
|
||||
func (c *GitCommand) NewBranch(name string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name))
|
||||
return c.OSCommand.RunCommand("git checkout -b %s", name)
|
||||
}
|
||||
|
||||
// CurrentBranchName is a function.
|
||||
func (c *GitCommand) CurrentBranchName() (string, error) {
|
||||
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||
if err != nil {
|
||||
branchName, err = c.OSCommand.RunCommandWithOutput("git rev-parse --short HEAD")
|
||||
if err != nil || branchName == "HEAD\n" {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||
match := re.FindStringSubmatch(output)
|
||||
branchName = match[1]
|
||||
}
|
||||
return utils.TrimTrailingNewline(branchName), nil
|
||||
}
|
||||
@@ -342,7 +353,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error {
|
||||
command = "git branch -D"
|
||||
}
|
||||
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("%s %s", command, branch))
|
||||
return c.OSCommand.RunCommand("%s %s", command, branch)
|
||||
}
|
||||
|
||||
// ListStash list stash
|
||||
@@ -352,7 +363,7 @@ func (c *GitCommand) ListStash() (string, error) {
|
||||
|
||||
// Merge merge
|
||||
func (c *GitCommand) Merge(branchName string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git merge --no-edit %s", branchName))
|
||||
return c.OSCommand.RunCommand("git merge --no-edit %s", branchName)
|
||||
}
|
||||
|
||||
// AbortMerge abort merge
|
||||
@@ -393,12 +404,12 @@ func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
// Pull pulls from repo
|
||||
func (c *GitCommand) Pull(ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit", ask)
|
||||
func (c *GitCommand) Pull(args string, ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit "+args, ask)
|
||||
}
|
||||
|
||||
// Push pushes to a branch
|
||||
func (c *GitCommand) Push(branchName string, force bool, upstream string, ask func(string) string) error {
|
||||
func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, ask func(string) string) error {
|
||||
forceFlag := ""
|
||||
if force {
|
||||
forceFlag = "--force-with-lease"
|
||||
@@ -409,18 +420,18 @@ func (c *GitCommand) Push(branchName string, force bool, upstream string, ask fu
|
||||
setUpstreamArg = "--set-upstream " + upstream
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("git push %s %s", forceFlag, setUpstreamArg)
|
||||
cmd := fmt.Sprintf("git push --follow-tags %s %s %s", forceFlag, setUpstreamArg, args)
|
||||
return c.OSCommand.DetectUnamePass(cmd, ask)
|
||||
}
|
||||
|
||||
// CatFile obtains the content of a file
|
||||
func (c *GitCommand) CatFile(fileName string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName)))
|
||||
return c.OSCommand.RunCommandWithOutput("cat %s", c.OSCommand.Quote(fileName))
|
||||
}
|
||||
|
||||
// StageFile stages a file
|
||||
func (c *GitCommand) StageFile(fileName string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git add %s", c.OSCommand.Quote(fileName)))
|
||||
return c.OSCommand.RunCommand("git add %s", c.OSCommand.Quote(fileName))
|
||||
}
|
||||
|
||||
// StageAll stages all files
|
||||
@@ -443,7 +454,7 @@ func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
|
||||
// renamed files look like "file1 -> file2"
|
||||
fileNames := strings.Split(fileName, " -> ")
|
||||
for _, name := range fileNames {
|
||||
if err := c.OSCommand.RunCommand(fmt.Sprintf(command, c.OSCommand.Quote(name))); err != nil {
|
||||
if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -487,7 +498,7 @@ 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 || file.HasMergeConflicts {
|
||||
if err := c.OSCommand.RunCommand(fmt.Sprintf("git reset -- %s", quotedFileName)); err != nil {
|
||||
if err := c.OSCommand.RunCommand("git reset -- %s", quotedFileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -501,16 +512,16 @@ func (c *GitCommand) DiscardAllFileChanges(file *File) error {
|
||||
// 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))
|
||||
return c.OSCommand.RunCommand("git checkout -- %s", quotedFileName)
|
||||
}
|
||||
|
||||
// Checkout checks out a branch, with --force if you set the force arg to true
|
||||
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
||||
func (c *GitCommand) Checkout(branch string, force bool) error {
|
||||
forceArg := ""
|
||||
if force {
|
||||
forceArg = "--force "
|
||||
}
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout %s %s", forceArg, branch))
|
||||
return c.OSCommand.RunCommand("git checkout %s %s", forceArg, branch)
|
||||
}
|
||||
|
||||
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
||||
@@ -527,11 +538,12 @@ func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
|
||||
// Currently it limits the result to 100 commits, but when we get async stuff
|
||||
// working we can do lazy loading
|
||||
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName))
|
||||
cmdStr := c.GetBranchGraphCmdStr(branchName)
|
||||
return c.OSCommand.RunCommandWithOutput(cmdStr)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName))
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName)
|
||||
return strings.TrimSpace(output), err
|
||||
}
|
||||
|
||||
@@ -540,41 +552,12 @@ func (c *GitCommand) Ignore(filename string) error {
|
||||
return c.OSCommand.AppendLineToFile(".gitignore", filename)
|
||||
}
|
||||
|
||||
// Show shows the diff of a commit
|
||||
func (c *GitCommand) Show(sha string) (string, error) {
|
||||
show, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color --no-renames %s", sha))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||
return fmt.Sprintf("git show --color --no-renames %s", sha)
|
||||
}
|
||||
|
||||
// if this is a merge commit, we need to go a step further and get the diff between the two branches we merged
|
||||
revList, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git rev-list -1 --merges %s^...%s", sha, sha))
|
||||
if err != nil {
|
||||
// turns out we get an error here when it's the first commit. We'll just return the original show
|
||||
return show, nil
|
||||
}
|
||||
if len(revList) == 0 {
|
||||
return show, nil
|
||||
}
|
||||
|
||||
// we want to pull out 1a6a69a and 3b51d7c from this:
|
||||
// commit ccc771d8b13d5b0d4635db4463556366470fd4f6
|
||||
// Merge: 1a6a69a 3b51d7c
|
||||
lines := utils.SplitLines(show)
|
||||
if len(lines) < 2 {
|
||||
return show, nil
|
||||
}
|
||||
|
||||
secondLineWords := strings.Split(lines[1], " ")
|
||||
if len(secondLineWords) < 3 {
|
||||
return show, nil
|
||||
}
|
||||
|
||||
mergeDiff, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git diff --color %s...%s", secondLineWords[1], secondLineWords[2]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return show + mergeDiff, nil
|
||||
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
||||
return fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName)
|
||||
}
|
||||
|
||||
// GetRemoteURL returns current repo remote url
|
||||
@@ -585,16 +568,22 @@ func (c *GitCommand) GetRemoteURL() string {
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
|
||||
_, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(
|
||||
_, err := c.OSCommand.RunCommandWithOutput(
|
||||
"git show-ref --verify -- refs/remotes/origin/%s",
|
||||
branch.Name,
|
||||
))
|
||||
)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Diff returns the diff of a file
|
||||
func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
||||
// for now we assume an error means the file was deleted
|
||||
s, _ := c.OSCommand.RunCommandWithOutput(c.DiffCmdStr(file, plain, cached))
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
||||
cachedArg := ""
|
||||
trackedArg := "--"
|
||||
colorArg := "--color"
|
||||
@@ -610,16 +599,12 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
||||
colorArg = ""
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||
|
||||
// for now we assume an error means the file was deleted
|
||||
s, _ := c.OSCommand.RunCommandWithOutput(command)
|
||||
return s
|
||||
return fmt.Sprintf("git diff %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||
c.Log.Warn(patch)
|
||||
filepath := filepath.Join(c.Config.GetUserConfigDir(), utils.GetCurrentRepoName(), time.Now().Format(time.StampNano)+".patch")
|
||||
filepath := filepath.Join(c.Config.GetUserConfigDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
|
||||
if err := c.OSCommand.CreateFileWithContent(filepath, patch); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -629,20 +614,21 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||
flagStr += " --" + flag
|
||||
}
|
||||
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)))
|
||||
return c.OSCommand.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))
|
||||
}
|
||||
|
||||
func (c *GitCommand) FastForward(branchName string) error {
|
||||
upstream := "origin" // hardcoding for now
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git fetch %s %s:%s", upstream, branchName, branchName))
|
||||
func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||
return c.OSCommand.RunCommand("git fetch %s %s:%s", remoteName, remoteBranchName, branchName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) RunSkipEditorCommand(command string) error {
|
||||
cmd := c.OSCommand.ExecutableFromString(command)
|
||||
lazyGitPath := c.OSCommand.GetLazygitPath()
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
|
||||
"EDITOR="+c.OSCommand.GetLazygitPath(),
|
||||
"EDITOR="+lazyGitPath,
|
||||
"VISUAL="+lazyGitPath,
|
||||
)
|
||||
return c.OSCommand.RunExecutable(cmd)
|
||||
}
|
||||
@@ -854,7 +840,7 @@ func (c *GitCommand) MoveTodoDown(index int) error {
|
||||
|
||||
// Revert reverts the selected commit by sha
|
||||
func (c *GitCommand) Revert(sha string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git revert %s", sha))
|
||||
return c.OSCommand.RunCommand("git revert %s", sha)
|
||||
}
|
||||
|
||||
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
||||
@@ -874,8 +860,7 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
|
||||
|
||||
// GetCommitFiles get the specified commit files
|
||||
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
||||
cmd := fmt.Sprintf("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||
files, err := c.OSCommand.RunCommandWithOutput(cmd)
|
||||
files, err := c.OSCommand.RunCommandWithOutput("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -901,18 +886,22 @@ func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager
|
||||
|
||||
// ShowCommitFile get the diff of specified commit file
|
||||
func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (string, error) {
|
||||
cmdStr := c.ShowCommitFileCmdStr(commitSha, fileName, plain)
|
||||
return c.OSCommand.RunCommandWithOutput(cmdStr)
|
||||
}
|
||||
|
||||
func (c *GitCommand) ShowCommitFileCmdStr(commitSha, fileName string, plain bool) string {
|
||||
colorArg := "--color"
|
||||
if plain {
|
||||
colorArg = ""
|
||||
}
|
||||
cmd := fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
||||
return c.OSCommand.RunCommandWithOutput(cmd)
|
||||
|
||||
return fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return c.OSCommand.RunCommand("git checkout %s %s", commitSha, fileName)
|
||||
}
|
||||
|
||||
// DiscardOldFileChanges discards changes to a file from an old commit
|
||||
@@ -922,7 +911,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*Commit, commitIndex int, f
|
||||
}
|
||||
|
||||
// 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.RunCommand("git cat-file -e HEAD^:%s", fileName); err != nil {
|
||||
if err := c.OSCommand.Remove(fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -956,26 +945,24 @@ 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")
|
||||
// ResetHardHead runs `git reset --hard`
|
||||
func (c *GitCommand) ResetHard(ref string) error {
|
||||
return c.OSCommand.RunCommand("git reset --hard " + ref)
|
||||
}
|
||||
|
||||
// ResetSoftHead runs `git reset --soft HEAD`
|
||||
func (c *GitCommand) ResetSoftHead() error {
|
||||
return c.OSCommand.RunCommand("git reset --soft HEAD")
|
||||
// ResetSoft runs `git reset --soft HEAD`
|
||||
func (c *GitCommand) ResetSoft(ref string) error {
|
||||
return c.OSCommand.RunCommand("git reset --soft " + ref)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return c.OSCommand.RunCommandWithOutput("git diff --color %s %s", sha1, sha2)
|
||||
}
|
||||
|
||||
// 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)
|
||||
return c.OSCommand.RunCommand("git commit --fixup=%s", sha)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -1059,5 +1046,75 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*Commit, commitIn
|
||||
}
|
||||
|
||||
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git branch -u %s", upstream))
|
||||
return c.OSCommand.RunCommand("git branch -u %s", upstream)
|
||||
}
|
||||
|
||||
func (c *GitCommand) AddRemote(name string, url string) error {
|
||||
return c.OSCommand.RunCommand("git remote add %s %s", name, url)
|
||||
}
|
||||
|
||||
func (c *GitCommand) RemoveRemote(name string) error {
|
||||
return c.OSCommand.RunCommand("git remote remove %s", name)
|
||||
}
|
||||
|
||||
func (c *GitCommand) IsHeadDetached() bool {
|
||||
err := c.OSCommand.RunCommand("git symbolic-ref -q HEAD")
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||
return c.OSCommand.RunCommand("git push %s --delete %s", remoteName, branchName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
|
||||
return c.OSCommand.RunCommand("git branch --set-upstream-to=%s/%s %s", remoteName, remoteBranchName, branchName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error {
|
||||
return c.OSCommand.RunCommand("git remote rename %s %s", oldRemoteName, newRemoteName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
|
||||
return c.OSCommand.RunCommand("git remote set-url %s %s", remoteName, updatedUrl)
|
||||
}
|
||||
|
||||
func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error {
|
||||
return c.OSCommand.RunCommand("git tag %s %s", tagName, commitSha)
|
||||
}
|
||||
|
||||
func (c *GitCommand) DeleteTag(tagName string) error {
|
||||
return c.OSCommand.RunCommand("git tag -d %s", tagName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) PushTag(remoteName string, tagName string) error {
|
||||
return c.OSCommand.RunCommand("git push %s %s", remoteName, tagName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) FetchRemote(remoteName string) error {
|
||||
return c.OSCommand.RunCommand("git fetch %s", remoteName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git reflog")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
commits := make([]*Commit, len(lines))
|
||||
re := regexp.MustCompile(`(\w+).*HEAD@\{\d+\}: (.*)`)
|
||||
for i, line := range lines {
|
||||
match := re.FindStringSubmatch(line)
|
||||
if len(match) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
commits[i] = &Commit{
|
||||
Sha: match[1],
|
||||
Name: match[2],
|
||||
Status: "reflog",
|
||||
}
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -58,14 +59,14 @@ func (f fileInfoMock) Sys() interface{} {
|
||||
func TestVerifyInGitRepo(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runCmd func(string) error
|
||||
runCmd func(string, ...interface{}) error
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Valid git repository",
|
||||
func(string) error {
|
||||
func(string, ...interface{}) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
@@ -74,7 +75,7 @@ func TestVerifyInGitRepo(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"Not a valid git repository",
|
||||
func(string) error {
|
||||
func(string, ...interface{}) error {
|
||||
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
|
||||
},
|
||||
func(err error) {
|
||||
@@ -318,21 +319,6 @@ func TestGitCommandGetStashEntries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandGetStashEntryDiff is a function.
|
||||
func TestGitCommandGetStashEntryDiff(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"stash", "show", "-p", "--color", "stash@{1}"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
|
||||
_, err := gitCmd.GetStashEntryDiff(1)
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestGitCommandGetStatusFiles is a function.
|
||||
func TestGitCommandGetStatusFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -990,7 +976,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
"Push with force disabled",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"push"}, args)
|
||||
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1003,7 +989,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
"Push with force enabled",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"push", "--force-with-lease"}, args)
|
||||
assert.EqualValues(t, []string{"push", "--follow-tags", "--force-with-lease"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1016,7 +1002,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
"Push with an error occurring",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"push"}, args)
|
||||
assert.EqualValues(t, []string{"push", "--follow-tags"}, args)
|
||||
return exec.Command("test")
|
||||
},
|
||||
false,
|
||||
@@ -1030,7 +1016,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = s.command
|
||||
err := gitCmd.Push("test", s.forcePush, "", func(passOrUname string) string {
|
||||
err := gitCmd.Push("test", s.forcePush, "", "", func(passOrUname string) string {
|
||||
return "\n"
|
||||
})
|
||||
s.test(err)
|
||||
@@ -1411,66 +1397,6 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandShow is a function.
|
||||
func TestGitCommandShow(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
arg string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"regular commit",
|
||||
"456abcde",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git show --color --no-renames 456abcde",
|
||||
Replace: "echo \"commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nblah\"",
|
||||
},
|
||||
{
|
||||
Expect: "git rev-list -1 --merges 456abcde^...456abcde",
|
||||
Replace: "echo",
|
||||
},
|
||||
}),
|
||||
func(result string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nblah\n", result)
|
||||
},
|
||||
},
|
||||
{
|
||||
"merge commit",
|
||||
"456abcde",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git show --color --no-renames 456abcde",
|
||||
Replace: "echo \"commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nMerge: 1a6a69a 3b51d7c\"",
|
||||
},
|
||||
{
|
||||
Expect: "git rev-list -1 --merges 456abcde^...456abcde",
|
||||
Replace: "echo aa30e006433628ba9281652952b34d8aacda9c01",
|
||||
},
|
||||
{
|
||||
Expect: "git diff --color 1a6a69a...3b51d7c",
|
||||
Replace: "echo blah",
|
||||
},
|
||||
}),
|
||||
func(result string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nMerge: 1a6a69a 3b51d7c\nblah\n", result)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
gitCmd := NewDummyGitCommand()
|
||||
|
||||
for _, s := range scenarios {
|
||||
gitCmd.OSCommand.command = s.command
|
||||
s.test(gitCmd.Show(s.arg))
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandCheckout is a function.
|
||||
func TestGitCommandCheckout(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -1523,7 +1449,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "-100", "test"}, args)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
@@ -1639,7 +1565,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"falls back to git rev-parse if symbolic-ref fails",
|
||||
"falls back to git `git branch --contains` if symbolic-ref fails",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
@@ -1647,9 +1573,9 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("test")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
case "branch":
|
||||
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||
return exec.Command("echo", "* master")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -2106,10 +2032,11 @@ func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandResetHardHead is a function.
|
||||
func TestGitCommandResetHardHead(t *testing.T) {
|
||||
// TestGitCommandResetHard is a function.
|
||||
func TestGitCommandResetHard(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
ref string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
@@ -2117,6 +2044,7 @@ func TestGitCommandResetHardHead(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
"HEAD",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git reset --hard HEAD`,
|
||||
@@ -2134,7 +2062,7 @@ func TestGitCommandResetHardHead(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCmd.OSCommand.command = s.command
|
||||
s.test(gitCmd.ResetHardHead())
|
||||
s.test(gitCmd.ResetHard(s.ref))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2174,6 +2102,37 @@ func TestGitCommandCreateFixupCommit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects
|
||||
// environment variables that suppress an interactive editor
|
||||
func TestGitCommandSkipEditorCommand(t *testing.T) {
|
||||
cmd := NewDummyGitCommand()
|
||||
|
||||
cmd.OSCommand.SetBeforeExecuteCmd(func(cmd *exec.Cmd) {
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmd.Env,
|
||||
regexp.MustCompile("^VISUAL="),
|
||||
"expected VISUAL to be set for a non-interactive external command",
|
||||
)
|
||||
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmd.Env,
|
||||
regexp.MustCompile("^EDITOR="),
|
||||
"expected EDITOR to be set for a non-interactive external command",
|
||||
)
|
||||
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmd.Env,
|
||||
regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"),
|
||||
"expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command",
|
||||
)
|
||||
})
|
||||
|
||||
cmd.RunSkipEditorCommand("true")
|
||||
}
|
||||
|
||||
func TestFindDotGitDir(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
|
||||
58
pkg/commands/loading_remotes.go
Normal file
58
pkg/commands/loading_remotes.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *GitCommand) GetRemotes() ([]*Remote, error) {
|
||||
// get remote branches
|
||||
unescaped := "git branch -r"
|
||||
remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(unescaped)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goGitRemotes, err := c.Repo.Remotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first step is to get our remotes from go-git
|
||||
remotes := make([]*Remote, len(goGitRemotes))
|
||||
for i, goGitRemote := range goGitRemotes {
|
||||
remoteName := goGitRemote.Config().Name
|
||||
|
||||
re := regexp.MustCompile(fmt.Sprintf(`%s\/([\S]+)`, remoteName))
|
||||
matches := re.FindAllStringSubmatch(remoteBranchesStr, -1)
|
||||
branches := make([]*RemoteBranch, len(matches))
|
||||
for j, match := range matches {
|
||||
branches[j] = &RemoteBranch{
|
||||
Name: match[1],
|
||||
RemoteName: remoteName,
|
||||
}
|
||||
}
|
||||
|
||||
remotes[i] = &Remote{
|
||||
Name: goGitRemote.Config().Name,
|
||||
Urls: goGitRemote.Config().URLs,
|
||||
Branches: branches,
|
||||
}
|
||||
}
|
||||
|
||||
// now lets sort our remotes by name alphabetically
|
||||
sort.Slice(remotes, func(i, j int) bool {
|
||||
// we want origin at the top because we'll be most likely to want it
|
||||
if remotes[i].Name == "origin" {
|
||||
return true
|
||||
}
|
||||
if remotes[j].Name == "origin" {
|
||||
return false
|
||||
}
|
||||
return strings.ToLower(remotes[i].Name) < strings.ToLower(remotes[j].Name)
|
||||
})
|
||||
|
||||
return remotes, nil
|
||||
}
|
||||
87
pkg/commands/loading_tags.go
Normal file
87
pkg/commands/loading_tags.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
const semverRegex = `v?((\d+\.?)+)([^\d]?.*)`
|
||||
|
||||
func convertToInt(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetTags() ([]*Tag, error) {
|
||||
// get remote branches
|
||||
remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(`git tag --list`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content := utils.TrimTrailingNewline(remoteBranchesStr)
|
||||
if content == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
split := strings.Split(content, "\n")
|
||||
|
||||
// first step is to get our remotes from go-git
|
||||
tags := make([]*Tag, len(split))
|
||||
for i, tagName := range split {
|
||||
|
||||
tags[i] = &Tag{
|
||||
Name: tagName,
|
||||
}
|
||||
}
|
||||
|
||||
// now lets sort our tags by name numerically
|
||||
re := regexp.MustCompile(semverRegex)
|
||||
|
||||
// the reason this is complicated is because we're both sorting alphabetically
|
||||
// and when we're dealing with semver strings
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
a := tags[i].Name
|
||||
b := tags[j].Name
|
||||
|
||||
matchA := re.FindStringSubmatch(a)
|
||||
matchB := re.FindStringSubmatch(b)
|
||||
|
||||
if len(matchA) > 0 && len(matchB) > 0 {
|
||||
numbersA := strings.Split(matchA[1], ".")
|
||||
numbersB := strings.Split(matchB[1], ".")
|
||||
k := 0
|
||||
for {
|
||||
if len(numbersA) == k && len(numbersB) == k {
|
||||
break
|
||||
}
|
||||
if len(numbersA) == k {
|
||||
return true
|
||||
}
|
||||
if len(numbersB) == k {
|
||||
return false
|
||||
}
|
||||
if convertToInt(numbersA[k]) < convertToInt(numbersB[k]) {
|
||||
return true
|
||||
}
|
||||
if convertToInt(numbersA[k]) > convertToInt(numbersB[k]) {
|
||||
return false
|
||||
}
|
||||
k++
|
||||
}
|
||||
|
||||
return strings.ToLower(matchA[3]) < strings.ToLower(matchB[3])
|
||||
}
|
||||
|
||||
return strings.ToLower(a) < strings.ToLower(b)
|
||||
})
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -35,6 +36,7 @@ type OSCommand struct {
|
||||
Platform *Platform
|
||||
Config config.AppConfigurer
|
||||
command func(string, ...string) *exec.Cmd
|
||||
beforeExecuteCmd func(*exec.Cmd)
|
||||
getGlobalGitConfig func(string) (string, error)
|
||||
getenv func(string) string
|
||||
}
|
||||
@@ -46,6 +48,7 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
|
||||
Platform: getPlatform(),
|
||||
Config: config,
|
||||
command: exec.Command,
|
||||
beforeExecuteCmd: func(*exec.Cmd) {},
|
||||
getGlobalGitConfig: gitconfig.Global,
|
||||
getenv: os.Getenv,
|
||||
}
|
||||
@@ -57,8 +60,21 @@ func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
|
||||
c.command = cmd
|
||||
}
|
||||
|
||||
func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) {
|
||||
c.beforeExecuteCmd = cmd
|
||||
}
|
||||
|
||||
// RunCommandWithOutput wrapper around commands returning their output and error
|
||||
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
||||
// NOTE: If you don't pass any formatArgs we'll just use the command directly,
|
||||
// however there's a bizarre compiler error/warning when you pass in a formatString
|
||||
// with a percent sign because it thinks it's supposed to be a formatString when
|
||||
// in that case it's not. To get around that error you'll need to define the string
|
||||
// in a variable and pass the variable into RunCommandWithOutput.
|
||||
func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) {
|
||||
command := formatString
|
||||
if formatArgs != nil {
|
||||
command = fmt.Sprintf(formatString, formatArgs...)
|
||||
}
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
cmd := c.ExecutableFromString(command)
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
@@ -66,6 +82,7 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
||||
|
||||
// RunExecutableWithOutput runs an executable file and returns its output
|
||||
func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) {
|
||||
c.beforeExecuteCmd(cmd)
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
}
|
||||
|
||||
@@ -114,8 +131,8 @@ func (c *OSCommand) DetectUnamePass(command string, ask func(string) string) err
|
||||
}
|
||||
|
||||
// RunCommand runs a command and just returns the error
|
||||
func (c *OSCommand) RunCommand(command string) error {
|
||||
_, err := c.RunCommandWithOutput(command)
|
||||
func (c *OSCommand) RunCommand(formatString string, formatArgs ...interface{}) error {
|
||||
_, err := c.RunCommandWithOutput(formatString, formatArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -298,6 +315,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
|
||||
// this is useful if you need to give your command some environment variables
|
||||
// before running it
|
||||
func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error {
|
||||
c.beforeExecuteCmd(cmd)
|
||||
out, err := cmd.CombinedOutput()
|
||||
outString := string(out)
|
||||
c.Log.Info(outString)
|
||||
@@ -316,7 +334,7 @@ func (c *OSCommand) GetLazygitPath() string {
|
||||
if err != nil {
|
||||
ex = os.Args[0] // fallback to the first call argument if needed
|
||||
}
|
||||
return filepath.ToSlash(ex)
|
||||
return `"` + filepath.ToSlash(ex) + `"`
|
||||
}
|
||||
|
||||
// RunCustomCommand returns the pointer to a custom command
|
||||
|
||||
24
pkg/commands/remote.go
Normal file
24
pkg/commands/remote.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Remote : A git remote
|
||||
type Remote struct {
|
||||
Name string
|
||||
Urls []string
|
||||
Selected bool
|
||||
Branches []*RemoteBranch
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a remote
|
||||
func (r *Remote) GetDisplayStrings(isFocused bool) []string {
|
||||
|
||||
branchCount := len(r.Branches)
|
||||
|
||||
return []string{r.Name, utils.ColoredString(fmt.Sprintf("%d branches", branchCount), color.FgBlue)}
|
||||
}
|
||||
19
pkg/commands/remote_branch.go
Normal file
19
pkg/commands/remote_branch.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Remote Branch : A git remote branch
|
||||
type RemoteBranch struct {
|
||||
Name string
|
||||
Selected bool
|
||||
RemoteName string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *RemoteBranch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
|
||||
return []string{displayName}
|
||||
}
|
||||
11
pkg/commands/tag.go
Normal file
11
pkg/commands/tag.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package commands
|
||||
|
||||
// Tag : A git tag
|
||||
type Tag struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a remote
|
||||
func (r *Tag) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.Name}
|
||||
}
|
||||
@@ -243,6 +243,7 @@ func GetDefaultConfig() []byte {
|
||||
scrollHeight: 2
|
||||
scrollPastBottom: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
theme:
|
||||
lightTheme: false
|
||||
activeBorderColor:
|
||||
@@ -265,6 +266,98 @@ update:
|
||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||
splashUpdatesIndex: 0
|
||||
confirmOnQuit: false
|
||||
keybinding:
|
||||
universal:
|
||||
quit: 'q'
|
||||
quit-alt1: '<c-c>'
|
||||
return: '<esc>'
|
||||
quitWithoutChangingDirectory: 'Q'
|
||||
togglePanel: '<tab>'
|
||||
prevItem: '<up>'
|
||||
nextItem: '<down>'
|
||||
prevItem-alt: 'k'
|
||||
nextItem-alt: 'j'
|
||||
prevBlock: '<left>'
|
||||
nextBlock: '<right>'
|
||||
prevBlock-alt: 'h'
|
||||
nextBlock-alt: 'l'
|
||||
optionMenu: 'x'
|
||||
optionMenu-alt1: '?'
|
||||
select: '<space>'
|
||||
goInto: '<enter>'
|
||||
remove: 'd'
|
||||
new: 'n'
|
||||
edit: 'e'
|
||||
openFile: 'o'
|
||||
scrollUpMain: '<pgup>'
|
||||
scrollDownMain: '<pgdown>'
|
||||
scrollUpMain-alt1: 'K'
|
||||
scrollDownMain-alt1: 'J'
|
||||
scrollUpMain-alt2: '<c-u>'
|
||||
scrollDownMain-alt2: '<c-d>'
|
||||
executeCustomCommand: 'X'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
refresh: 'R'
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
files:
|
||||
commitChanges: 'c'
|
||||
commitChangesWithoutHook: 'w'
|
||||
amendLastCommit: 'A'
|
||||
commitChangesWithEditor: 'C'
|
||||
ignoreFile: 'i'
|
||||
refreshFiles: 'r'
|
||||
stashAllChanges: 's'
|
||||
viewStashOptions: 'S'
|
||||
toggleStagedAll: 'a'
|
||||
viewResetOptions: 'D'
|
||||
fetch: 'f'
|
||||
branches:
|
||||
createPullRequest: 'o'
|
||||
checkoutBranchByName: 'c'
|
||||
forceCheckoutBranch: 'F'
|
||||
rebaseBranch: 'r'
|
||||
mergeIntoCurrentBranch: 'M'
|
||||
viewGitFlowOptions: 'i'
|
||||
fastForward: 'f'
|
||||
pushTag: 'P'
|
||||
setUpstream: 'u'
|
||||
fetchRemote: 'f'
|
||||
commits:
|
||||
squashDown: 's'
|
||||
renameCommit: 'r'
|
||||
renameCommitWithEditor: 'R'
|
||||
viewResetOptions: 'g'
|
||||
markCommitAsFixup: 'f'
|
||||
createFixupCommit: 'F'
|
||||
squashAboveCommits: 'S'
|
||||
moveDownCommit: '<c-j>'
|
||||
moveUpCommit: '<c-k>'
|
||||
amendToCommit: 'A'
|
||||
pickCommit: 'p'
|
||||
revertCommit: 't'
|
||||
cherryPickCopy: 'c'
|
||||
cherryPickCopyRange: 'C'
|
||||
pasteCommits: 'v'
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
checkoutCommitFile: 'c'
|
||||
main:
|
||||
toggleDragSelect: 'v'
|
||||
toggleDragSelect-alt: 'V'
|
||||
toggleSelectHunk: 'a'
|
||||
pickBothHunks: 'b'
|
||||
undo: 'z'
|
||||
`)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -49,15 +51,26 @@ func (m *statusManager) getStatusString() string {
|
||||
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
||||
go func() {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
gui.statusManager.addWaitingStatus(name)
|
||||
return nil
|
||||
})
|
||||
gui.statusManager.addWaitingStatus(name)
|
||||
|
||||
defer gui.g.Update(func(g *gocui.Gui) error {
|
||||
defer func() {
|
||||
gui.statusManager.removeStatus(name)
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.Log.Warn(appStatus)
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
|
||||
gui.Log.Warn(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -21,14 +20,6 @@ func (gui *Gui) getSelectedBranch() *commands.Branch {
|
||||
return gui.State.Branches[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesClick(g *gocui.Gui, v *gocui.View) error {
|
||||
itemCount := len(gui.State.Branches)
|
||||
handleSelect := gui.handleBranchSelect
|
||||
selectedLine := &gui.State.Panels.Branches.SelectedLine
|
||||
|
||||
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
||||
}
|
||||
|
||||
// may want to standardise how these select methods work
|
||||
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
@@ -45,45 +36,58 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
// This really shouldn't happen: there should always be a master branch
|
||||
if len(gui.State.Branches) == 0 {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
}()
|
||||
go func() {
|
||||
upstream, _ := gui.GitCommand.GetUpstreamForBranch(branch.Name)
|
||||
if strings.Contains(upstream, "no upstream configured for branch") {
|
||||
upstream = gui.Tr.SLocalize("notTrackingRemote")
|
||||
}
|
||||
graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
|
||||
if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") {
|
||||
graph = gui.Tr.SLocalize("NoTrackingThisBranch")
|
||||
}
|
||||
_ = gui.renderString(g, "main", fmt.Sprintf("%s → %s\n\n%s", utils.ColoredString(branch.Name, color.FgGreen), utils.ColoredString(upstream, color.FgRed), graph))
|
||||
}()
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
||||
// here we tell the selected branch that it is selected.
|
||||
// this is necessary for showing stats on a branch that is selected, because
|
||||
// the displaystring function doesn't have access to gui state to tell if it's selected
|
||||
for i, branch := range gui.State.Branches {
|
||||
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
|
||||
}
|
||||
return gui.newTask("branches", func(stop chan struct{}) error {
|
||||
// here we tell the selected branch that it is selected.
|
||||
// this is necessary for showing stats on a branch that is selected, because
|
||||
// the displaystring function doesn't have access to gui state to tell if it's selected
|
||||
for i, branch := range gui.State.Branches {
|
||||
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
|
||||
}
|
||||
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches)
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches)
|
||||
})
|
||||
}
|
||||
|
||||
// gui.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a state.Branches array to pick the current branch from
|
||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
if err := gui.refreshRemotes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshTags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand)
|
||||
if err != nil {
|
||||
@@ -91,9 +95,12 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
}
|
||||
gui.State.Branches = builder.Build()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
// TODO: if we're in the remotes view and we've just deleted a remote we need to refresh accordingly
|
||||
if gui.getBranchesView().Context == "local-branches" {
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.refreshStatus(g)
|
||||
@@ -101,32 +108,20 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
func (gui *Gui) renderLocalBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
panelState := gui.State.Panels.Branches
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleBranchSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "local-branches" {
|
||||
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Branches
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleBranchSelect(gui.g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific functions
|
||||
@@ -139,7 +134,7 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
return gui.handleCheckoutBranch(branch.Name)
|
||||
return gui.handleCheckoutRef(branch.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -176,17 +171,17 @@ 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 {
|
||||
func (gui *Gui) handleCheckoutRef(ref string) error {
|
||||
if err := gui.GitCommand.Checkout(ref, 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") {
|
||||
// offer to autostash changes
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, 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 {
|
||||
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + ref); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
|
||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
@@ -209,18 +204,27 @@ func (gui *Gui) handleCheckoutBranch(branchName string) error {
|
||||
}
|
||||
|
||||
gui.State.Panels.Branches.SelectedLine = 0
|
||||
gui.State.Panels.Commits.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 {
|
||||
return gui.handleCheckoutBranch(gui.trimmedContent(v))
|
||||
return gui.handleCheckoutRef(gui.trimmedContent(v))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||
if len(gui.State.Branches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Branches[0]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.State.Branches[0]
|
||||
branch := gui.getCheckedOutBranch()
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"NewBranchNameBranchOff",
|
||||
Teml{
|
||||
@@ -250,7 +254,7 @@ func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
if selectedBranch == nil {
|
||||
return nil
|
||||
}
|
||||
checkedOutBranch := gui.State.Branches[0]
|
||||
checkedOutBranch := gui.getCheckedOutBranch()
|
||||
if checkedOutBranch.Name == selectedBranch.Name {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
|
||||
}
|
||||
@@ -283,44 +287,54 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
checkedOutBranch := gui.State.Branches[0].Name
|
||||
selectedBranch := gui.getSelectedBranch().Name
|
||||
if checkedOutBranch == selectedBranch {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
|
||||
func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
|
||||
if gui.GitCommand.IsHeadDetached() {
|
||||
return gui.createErrorPanel(gui.g, "Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
|
||||
}
|
||||
checkedOutBranchName := gui.getCheckedOutBranch().Name
|
||||
if checkedOutBranchName == branchName {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
|
||||
}
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmMerge",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranch,
|
||||
"checkedOutBranch": checkedOutBranchName,
|
||||
"selectedBranch": branchName,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
err := gui.GitCommand.Merge(selectedBranch)
|
||||
err := gui.GitCommand.Merge(branchName)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
|
||||
checkedOutBranch := gui.State.Branches[0].Name
|
||||
selectedBranch := gui.getSelectedBranch().Name
|
||||
if selectedBranch == checkedOutBranch {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
||||
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranchName := gui.getSelectedBranch().Name
|
||||
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRebaseOntoLocalBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranchName := gui.getSelectedBranch().Name
|
||||
return gui.handleRebaseOntoBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
|
||||
checkedOutBranch := gui.getCheckedOutBranch().Name
|
||||
if selectedBranchName == checkedOutBranch {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
||||
}
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmRebase",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranch,
|
||||
"selectedBranch": selectedBranchName,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
err := gui.GitCommand.RebaseBranch(selectedBranch)
|
||||
err := gui.GitCommand.RebaseBranch(selectedBranchName)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
@@ -339,17 +353,27 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
||||
if branch.Pushables != "0" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush"))
|
||||
}
|
||||
upstream := "origin" // hardcoding for now
|
||||
|
||||
upstream, err := gui.GitCommand.GetUpstreamForBranch(branch.Name)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
split := strings.Split(upstream, "/")
|
||||
remoteName := split[0]
|
||||
remoteBranchName := strings.Join(split[1:], "/")
|
||||
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"Fetching",
|
||||
Teml{
|
||||
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
|
||||
"from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName),
|
||||
"to": branch.Name,
|
||||
},
|
||||
)
|
||||
go func() {
|
||||
_ = gui.createLoaderPanel(gui.g, v, message)
|
||||
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
|
||||
|
||||
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
} else {
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
@@ -358,3 +382,50 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
||||
contexts := []string{"local-branches", "remotes", "tags"}
|
||||
branchesView := gui.getBranchesView()
|
||||
branchesView.TabIndex = tabIndex
|
||||
|
||||
return gui.switchBranchesPanelContext(contexts[tabIndex])
|
||||
}
|
||||
|
||||
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
branchesView.Context = context
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"local-branches": 0,
|
||||
"remotes": 1,
|
||||
"remote-branches": 1,
|
||||
"tags": 2,
|
||||
}
|
||||
|
||||
branchesView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
switch context {
|
||||
case "local-branches":
|
||||
return gui.renderLocalBranchesWithSelection()
|
||||
case "remotes":
|
||||
return gui.renderRemotesWithSelection()
|
||||
case "remote-branches":
|
||||
return gui.renderRemoteBranchesWithSelection()
|
||||
case "tags":
|
||||
return gui.renderTagsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNextBranchesTab(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.onBranchesTabClick(
|
||||
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePrevBranchesTab(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.onBranchesTabClick(
|
||||
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
gui.State.Panels.LineByLine = nil
|
||||
if gui.currentViewName() == "commitFiles" {
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
}
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
@@ -43,33 +45,19 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
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, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(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)
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
return gui.switchFocus(g, v, gui.getCommitsView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -208,9 +196,7 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.changeContext("patch-building"); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.changeMainViewsContext("patch-building")
|
||||
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -83,3 +83,38 @@ func (gui *Gui) RenderCommitLength() {
|
||||
v := gui.getCommitMessageView()
|
||||
v.Subtitle = gui.getBufferLength(v)
|
||||
}
|
||||
|
||||
// we've just copy+pasted the editor from gocui to here so that we can also re-
|
||||
// render the commit message length on each keypress
|
||||
func (gui *Gui) commitMessageEditor(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
|
||||
case key == gocui.KeyCtrlU:
|
||||
v.EditDeleteToStartOfLine()
|
||||
case key == gocui.KeyCtrlA:
|
||||
v.EditGotoToStartOfLine()
|
||||
case key == gocui.KeyCtrlE:
|
||||
v.EditGotoToEndOfLine()
|
||||
default:
|
||||
v.EditWrite(ch)
|
||||
}
|
||||
|
||||
gui.RenderCommitLength()
|
||||
}
|
||||
|
||||
@@ -23,27 +23,6 @@ func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit {
|
||||
return gui.State.Commits[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
prevSelectedLineIdx := gui.State.Panels.Commits.SelectedLine
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLineIdx > len(gui.State.Commits)-1 {
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = newSelectedLineIdx
|
||||
|
||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
||||
return gui.handleSwitchToCommitFilesPanel(gui.g, v)
|
||||
} else {
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
@@ -58,13 +37,23 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
state := gui.State.Panels.Commits
|
||||
if state.SelectedLine > 20 && state.LimitCommits {
|
||||
state.LimitCommits = false
|
||||
go func() {
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
gui.getSecondaryView().Title = "Custom Patch"
|
||||
gui.State.Panels.LineByLine = nil
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits), v); err != nil {
|
||||
@@ -76,42 +65,33 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
commitText, err := gui.GitCommand.Show(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
return gui.renderString(g, "main", commitText)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
builder, err := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commits, err := builder.GetCommits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Commits = commits
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||
|
||||
isFocused := gui.g.CurrentView().Name() == "commits"
|
||||
list, err := utils.RenderList(gui.State.Commits, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := gui.getCommitsView()
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
|
||||
// I think this is here for the sake of some kind of rebasing thing
|
||||
gui.refreshStatus(g)
|
||||
if g.CurrentView() == v {
|
||||
gui.handleCommitSelect(g, v)
|
||||
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
return err
|
||||
}
|
||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.Context == "patch-building") {
|
||||
|
||||
// doing this async because it shouldn't hold anything up
|
||||
go func() {
|
||||
if err := gui.refreshReflogCommits(); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.MainContext == "patch-building") {
|
||||
return gui.refreshCommitFilesView()
|
||||
}
|
||||
return nil
|
||||
@@ -119,32 +99,25 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Commits
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
builder, err := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Commits
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
commits, err := builder.GetCommits(gui.State.Panels.Commits.LimitCommits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
gui.State.Commits = commits
|
||||
|
||||
if gui.getCommitsView().Context == "branch-commits" {
|
||||
if err := gui.renderBranchCommitsWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// specific functions
|
||||
@@ -493,7 +466,7 @@ func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
// get selected commit
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
// if already selected commit delete
|
||||
@@ -516,7 +489,7 @@ func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.renderString(g, "main", commitText)
|
||||
return gui.newStringTask("main", commitText)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -633,3 +606,98 @@ func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTagCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
// TODO: bring up menu asking if you want to make a lightweight or annotated tag
|
||||
// if annotated, switch to a subprocess to create the message
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.handleCreateLightweightTag(commit.Sha)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateLightweightTag(commitSha string) error {
|
||||
return gui.createPromptPanel(gui.g, gui.getCommitsView(), gui.Tr.SLocalize("TagNameTitle"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), commitSha); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshTags(); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.handleCommitSelect(g, v)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, gui.getCommitsView(), true, gui.Tr.SLocalize("checkoutCommit"), gui.Tr.SLocalize("SureCheckoutThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutRef(commit.Sha)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderBranchCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||
if err := gui.renderListPanel(commitsView, gui.State.Commits); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
||||
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitsTabClick(tabIndex int) error {
|
||||
contexts := []string{"branch-commits", "reflog-commits"}
|
||||
commitsView := gui.getCommitsView()
|
||||
commitsView.TabIndex = tabIndex
|
||||
|
||||
return gui.switchCommitsPanelContext(contexts[tabIndex])
|
||||
}
|
||||
|
||||
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||
commitsView := gui.getCommitsView()
|
||||
commitsView.Context = context
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"branch-commits": 0,
|
||||
"reflog-commits": 1,
|
||||
}
|
||||
|
||||
commitsView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
switch context {
|
||||
case "branch-commits":
|
||||
return gui.renderBranchCommitsWithSelection()
|
||||
case "reflog-commits":
|
||||
return gui.renderReflogCommitsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNextCommitsTab(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.onCommitsTabClick(
|
||||
utils.ModuloWithWrap(v.TabIndex+1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePrevCommitsTab(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.onCommitsTabClick(
|
||||
utils.ModuloWithWrap(v.TabIndex-1, len(v.Tabs)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@ import (
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
if function != nil {
|
||||
if err := function(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.closeConfirmationPrompt(g, returnFocusOnClose)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +33,7 @@ func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui, returnFocusOnClose bool) e
|
||||
if err != nil {
|
||||
return nil // if it's already been closed we can just return
|
||||
}
|
||||
view.Editable = false
|
||||
if returnFocusOnClose {
|
||||
if err := gui.returnFocus(g, view); err != nil {
|
||||
panic(err)
|
||||
@@ -64,20 +67,6 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt s
|
||||
height/2 + panelHeight/2
|
||||
}
|
||||
|
||||
func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, initialContent string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||
gui.onNewPopupPanel()
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, initialContent, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmationView.Editable = true
|
||||
if err := gui.renderString(g, "confirmation", initialContent); err != nil {
|
||||
return err
|
||||
}
|
||||
// in the future we might want to give createPromptPanel the returnFocusOnClose arg too, but for now we're always setting it to true
|
||||
return gui.setKeyBindings(g, handleConfirm, nil, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, true, prompt)
|
||||
confirmationView, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0)
|
||||
@@ -86,6 +75,9 @@ func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt
|
||||
return nil, err
|
||||
}
|
||||
confirmationView.HasLoader = hasLoader
|
||||
if hasLoader {
|
||||
gui.g.StartTicking()
|
||||
}
|
||||
confirmationView.Title = title
|
||||
confirmationView.Wrap = true
|
||||
confirmationView.FgColor = theme.GocuiDefaultTextColor
|
||||
@@ -105,35 +97,31 @@ func (gui *Gui) onNewPopupPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
|
||||
return gui.createPopupPanel(g, currentView, "", prompt, true, true, nil, nil)
|
||||
}
|
||||
|
||||
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
|
||||
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, returnFocusOnClose, handleConfirm, handleClose)
|
||||
}
|
||||
|
||||
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, editable bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
gui.onNewPopupPanel()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
// delete the existing confirmation panel if it exists
|
||||
if view, _ := g.View("confirmation"); view != nil {
|
||||
if err := gui.closeConfirmationPrompt(g, true); err != nil {
|
||||
errMessage := gui.Tr.TemplateLocalize(
|
||||
"CantCloseConfirmationPrompt",
|
||||
Teml{
|
||||
"error": err.Error(),
|
||||
},
|
||||
)
|
||||
gui.Log.Error(errMessage)
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmationView.Editable = false
|
||||
confirmationView.Editable = editable
|
||||
if editable {
|
||||
go func() {
|
||||
// TODO: remove this wait (right now if you remove it the EditGotoToEndOfLine method doesn't seem to work)
|
||||
time.Sleep(time.Millisecond)
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
confirmationView.EditGotoToEndOfLine()
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,6 +130,19 @@ func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
|
||||
return gui.createPopupPanel(g, currentView, "", prompt, true, true, false, nil, nil)
|
||||
}
|
||||
|
||||
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
|
||||
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, returnFocusOnClose, false, handleConfirm, handleClose)
|
||||
}
|
||||
|
||||
func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, initialContent string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||
return gui.createPopupPanel(gui.g, currentView, title, initialContent, false, true, true, handleConfirm, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) error {
|
||||
actions := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
@@ -153,14 +154,14 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
||||
if err := gui.renderString(g, "options", actions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||
}
|
||||
|
||||
func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, true, nil, nil)
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, true, false, nil, nil)
|
||||
}
|
||||
|
||||
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||
|
||||
@@ -1,45 +1,20 @@
|
||||
package gui
|
||||
|
||||
func (gui *Gui) changeContext(context string) error {
|
||||
oldContext := gui.State.Context
|
||||
|
||||
if gui.State.Context == context {
|
||||
return nil
|
||||
// changeContext is a helper function for when we want to change a 'main' context
|
||||
// which currently just means a context that affects both the main and secondary views
|
||||
// other views can have their context changed directly but this function helps
|
||||
// keep the main and secondary views in sync
|
||||
func (gui *Gui) changeMainViewsContext(context string) {
|
||||
if gui.State.MainContext == context {
|
||||
return
|
||||
}
|
||||
|
||||
contextMap := gui.GetContextMap()
|
||||
|
||||
oldBindings := contextMap[oldContext]
|
||||
for _, binding := range oldBindings {
|
||||
if err := gui.g.DeleteKeybinding(binding.ViewName, binding.Key, binding.Modifier); err != nil {
|
||||
return err
|
||||
}
|
||||
switch context {
|
||||
case "normal", "patch-building", "staging", "merging":
|
||||
gui.getMainView().Context = context
|
||||
gui.getSecondaryView().Context = context
|
||||
}
|
||||
|
||||
bindings := contextMap[context]
|
||||
for _, binding := range bindings {
|
||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Context = context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setInitialContext() error {
|
||||
contextMap := gui.GetContextMap()
|
||||
|
||||
initialContext := "normal"
|
||||
|
||||
bindings := contextMap[initialContext]
|
||||
for _, binding := range bindings {
|
||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Context = initialContext
|
||||
|
||||
return nil
|
||||
gui.State.MainContext = context
|
||||
return
|
||||
}
|
||||
|
||||
149
pkg/gui/file_watching.go
Normal file
149
pkg/gui/file_watching.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// macs for some bizarre reason cap the number of watchable files to 256.
|
||||
// there's no obvious platform agonstic way to check the situation of the user's
|
||||
// computer so we're just arbitrarily capping at 200. This isn't so bad because
|
||||
// file watching is only really an added bonus for faster refreshing.
|
||||
const MAX_WATCHED_FILES = 50
|
||||
|
||||
type fileWatcher struct {
|
||||
Watcher *fsnotify.Watcher
|
||||
WatchedFilenames []string
|
||||
Log *logrus.Entry
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
func NewFileWatcher(log *logrus.Entry) *fileWatcher {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
log.Error(err)
|
||||
return &fileWatcher{
|
||||
Disabled: true,
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return &fileWatcher{
|
||||
Disabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
return &fileWatcher{
|
||||
Watcher: watcher,
|
||||
Log: log,
|
||||
WatchedFilenames: make([]string, 0, MAX_WATCHED_FILES),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fileWatcher) watchingFilename(filename string) bool {
|
||||
for _, watchedFilename := range w.WatchedFilenames {
|
||||
if watchedFilename == filename {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *fileWatcher) popOldestFilename() {
|
||||
// shift the last off the array to make way for this one
|
||||
oldestFilename := w.WatchedFilenames[0]
|
||||
w.WatchedFilenames = w.WatchedFilenames[1:]
|
||||
if err := w.Watcher.Remove(oldestFilename); err != nil {
|
||||
// swallowing errors here because it doesn't really matter if we can't unwatch a file
|
||||
w.Log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fileWatcher) watchFilename(filename string) {
|
||||
w.Log.Warn(filename)
|
||||
if err := w.Watcher.Add(filename); err != nil {
|
||||
// swallowing errors here because it doesn't really matter if we can't watch a file
|
||||
w.Log.Warn(err)
|
||||
}
|
||||
|
||||
// assume we're watching it now to be safe
|
||||
w.WatchedFilenames = append(w.WatchedFilenames, filename)
|
||||
}
|
||||
|
||||
func (w *fileWatcher) addFilesToFileWatcher(files []*commands.File) error {
|
||||
if w.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// watch the files for changes
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files[0:min(MAX_WATCHED_FILES, len(files))] {
|
||||
if file.Deleted {
|
||||
continue
|
||||
}
|
||||
filename := filepath.Join(dirName, file.Name)
|
||||
if w.watchingFilename(filename) {
|
||||
continue
|
||||
}
|
||||
if len(w.WatchedFilenames) > MAX_WATCHED_FILES {
|
||||
w.popOldestFilename()
|
||||
}
|
||||
|
||||
w.watchFilename(filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a int, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// NOTE: given that we often edit files ourselves, this may make us end up refreshing files too often
|
||||
// TODO: consider watching the whole directory recursively (could be more expensive)
|
||||
func (gui *Gui) watchFilesForChanges() {
|
||||
gui.fileWatcher = NewFileWatcher(gui.Log)
|
||||
if gui.fileWatcher.Disabled {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// watch for events
|
||||
case event := <-gui.fileWatcher.Watcher.Events:
|
||||
if event.Op == fsnotify.Chmod {
|
||||
// for some reason we pick up chmod events when they don't actually happen
|
||||
continue
|
||||
}
|
||||
// only refresh if we're not already
|
||||
if !gui.State.IsRefreshingFiles {
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
err = gui.createErrorPanel(gui.g, err.Error())
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watch for errors
|
||||
case err := <-gui.fileWatcher.Watcher.Errors:
|
||||
if err != nil {
|
||||
gui.Log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -27,82 +26,71 @@ func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
|
||||
return gui.State.Files[selectedLine], nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLine
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLineIdx > len(gui.State.Files)-1 {
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
gui.State.Panels.Files.SelectedLine = newSelectedLineIdx
|
||||
|
||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
||||
return gui.handleFilePress(gui.g, v)
|
||||
} else {
|
||||
return gui.handleFileSelect(gui.g, v, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bool) error {
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := gui.getSelectedFile(g)
|
||||
func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
gui.State.SplitMainPanel = false
|
||||
gui.getMainView().Title = ""
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), v); err != nil {
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), gui.getFilesView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("MergeConflictsTitle")
|
||||
gui.State.SplitMainPanel = false
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
content := gui.GitCommand.Diff(file, false, false)
|
||||
contentCached := gui.GitCommand.Diff(file, false, true)
|
||||
leftContent := content
|
||||
if !alreadySelected {
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getSecondaryView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
gui.State.SplitMainPanel = true
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, true)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("secondary", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
gui.State.SplitMainPanel = false
|
||||
if file.HasUnstagedChanges {
|
||||
leftContent = content
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
} else {
|
||||
leftContent = contentCached
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
}
|
||||
}
|
||||
|
||||
if alreadySelected {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
if err := gui.setViewContent(gui.g, gui.getSecondaryView(), contentCached); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.setViewContent(gui.g, gui.getMainView(), leftContent)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
if err := gui.renderString(g, "secondary", contentCached); err != nil {
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", leftContent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFiles() error {
|
||||
gui.State.RefreshingFilesMutex.Lock()
|
||||
gui.State.IsRefreshingFiles = true
|
||||
defer func() {
|
||||
gui.State.IsRefreshingFiles = false
|
||||
gui.State.RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
selectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
|
||||
filesView := gui.getFilesView()
|
||||
@@ -124,10 +112,10 @@ func (gui *Gui) refreshFiles() error {
|
||||
}
|
||||
fmt.Fprint(filesView, list)
|
||||
|
||||
if filesView == g.CurrentView() {
|
||||
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
||||
return gui.handleFileSelect(g, filesView, alreadySelected)
|
||||
return gui.selectFile(alreadySelected)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -135,28 +123,6 @@ func (gui *Gui) refreshFiles() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Files
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
|
||||
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Files
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
|
||||
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) stagedFiles() []*commands.File {
|
||||
@@ -207,9 +173,7 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
|
||||
}
|
||||
if err := gui.changeContext("staging"); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.changeMainViewsContext("staging")
|
||||
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -239,7 +203,7 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleFileSelect(g, v, true)
|
||||
return gui.selectFile(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) allFilesStaged() bool {
|
||||
@@ -251,6 +215,14 @@ func (gui *Gui) allFilesStaged() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (gui *Gui) focusAndSelectFile(g *gocui.Gui, v *gocui.View) error {
|
||||
if _, err := gui.g.SetCurrentView("files"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.selectFile(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
||||
var err error
|
||||
if gui.allFilesStaged() {
|
||||
@@ -266,7 +238,7 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
return gui.selectFile(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -385,6 +357,11 @@ func (gui *Gui) refreshStateFiles() error {
|
||||
// get files to stage
|
||||
files := gui.GitCommand.GetStatusFiles()
|
||||
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files)
|
||||
|
||||
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files))
|
||||
return gui.updateWorkTreeState()
|
||||
}
|
||||
@@ -395,15 +372,15 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return "", err
|
||||
}
|
||||
return "", gui.renderString(g, "main", gui.Tr.SLocalize("NoFilesDisplay"))
|
||||
return "", gui.newStringTask("main", gui.Tr.SLocalize("NoFilesDisplay"))
|
||||
}
|
||||
if item.Type != "file" {
|
||||
return "", gui.renderString(g, "main", gui.Tr.SLocalize("NotAFile"))
|
||||
return "", gui.newStringTask("main", gui.Tr.SLocalize("NotAFile"))
|
||||
}
|
||||
cat, err := gui.GitCommand.CatFile(item.Name)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return "", gui.renderString(g, "main", err.Error())
|
||||
return "", gui.newStringTask("main", err.Error())
|
||||
}
|
||||
return cat, nil
|
||||
}
|
||||
@@ -416,6 +393,17 @@ func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
if pullables == "?" {
|
||||
// see if we have this branch in our config with an upstream
|
||||
conf, err := gui.GitCommand.Repo.Config()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
for branchName, branch := range conf.Branches {
|
||||
if branchName == currentBranchName {
|
||||
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranchName, func(g *gocui.Gui, v *gocui.View) error {
|
||||
upstream := gui.trimmedContent(v)
|
||||
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
|
||||
@@ -425,21 +413,21 @@ func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
return gui.createErrorPanel(gui.g, errorMessage)
|
||||
}
|
||||
return gui.pullFiles(v)
|
||||
return gui.pullFiles(v, "")
|
||||
})
|
||||
}
|
||||
|
||||
return gui.pullFiles(v)
|
||||
return gui.pullFiles(v, "")
|
||||
}
|
||||
|
||||
func (gui *Gui) pullFiles(v *gocui.View) error {
|
||||
func (gui *Gui) pullFiles(v *gocui.View, args string) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PullWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
unamePassOpend := false
|
||||
err := gui.GitCommand.Pull(func(passOrUname string) string {
|
||||
err := gui.GitCommand.Pull(args, func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(gui.g, v, passOrUname)
|
||||
})
|
||||
@@ -449,14 +437,14 @@ func (gui *Gui) pullFiles(v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstream string) error {
|
||||
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstream string, args string) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PushWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
unamePassOpend := false
|
||||
branchName := gui.State.Branches[0].Name
|
||||
err := gui.GitCommand.Push(branchName, force, upstream, func(passOrUname string) string {
|
||||
branchName := gui.getCheckedOutBranch().Name
|
||||
err := gui.GitCommand.Push(branchName, force, upstream, args, func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(g, v, passOrUname)
|
||||
})
|
||||
@@ -474,14 +462,25 @@ func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
if pullables == "?" {
|
||||
// see if we have this branch in our config with an upstream
|
||||
conf, err := gui.GitCommand.Repo.Config()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
for branchName, branch := range conf.Branches {
|
||||
if branchName == currentBranchName {
|
||||
return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+currentBranchName, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(g, v, false, gui.trimmedContent(v))
|
||||
return gui.pushWithForceFlag(g, v, false, gui.trimmedContent(v), "")
|
||||
})
|
||||
} else if pullables == "0" {
|
||||
return gui.pushWithForceFlag(g, v, false, "")
|
||||
return gui.pushWithForceFlag(g, v, false, "", "")
|
||||
}
|
||||
return gui.createConfirmationPanel(g, nil, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(g, v, true, "")
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(g, v, true, "", "")
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -496,9 +495,7 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
if !file.HasInlineMergeConflicts {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons"))
|
||||
}
|
||||
if err := gui.changeContext("merging"); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.changeMainViewsContext("merging")
|
||||
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -535,22 +532,11 @@ type discardOption struct {
|
||||
description string
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -602,62 +588,6 @@ func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
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)
|
||||
|
||||
106
pkg/gui/git_flow.go
Normal file
106
pkg/gui/git_flow.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type gitFlowOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
}
|
||||
|
||||
func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) error {
|
||||
// need to find out what kind of branch this is
|
||||
prefix := strings.SplitAfterN(branchName, "/", 2)[0]
|
||||
suffix := strings.Replace(branchName, prefix, "", 1)
|
||||
|
||||
branchType := ""
|
||||
for _, line := range strings.Split(strings.TrimSpace(gitFlowConfig), "\n") {
|
||||
if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) {
|
||||
// now I just need to how do you say
|
||||
regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*")
|
||||
matches := regex.FindAllStringSubmatch(line, 1)
|
||||
|
||||
if len(matches) > 0 && len(matches[0]) > 1 {
|
||||
branchType = matches[0][1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if branchType == "" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NotAGitFlowBranch"))
|
||||
}
|
||||
|
||||
subProcess := gui.OSCommand.PrepareSubProcess("git", "flow", branchType, "finish", suffix)
|
||||
gui.SubProcess = subProcess
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *gitFlowOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateGitFlowMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get config
|
||||
gitFlowConfig, err := gui.OSCommand.RunCommandWithOutput("git config --local --get-regexp gitflow")
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, "You need to install git-flow and enable it in this repo to use git-flow features")
|
||||
}
|
||||
|
||||
startHandler := func(branchType string) func() error {
|
||||
return func() error {
|
||||
title := gui.Tr.TemplateLocalize("NewBranchNamePrompt", map[string]interface{}{"branchType": branchType})
|
||||
return gui.createPromptPanel(gui.g, gui.getMenuView(), title, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
name := gui.trimmedContent(v)
|
||||
subProcess := gui.OSCommand.PrepareSubProcess("git", "flow", branchType, "start", name)
|
||||
gui.SubProcess = subProcess
|
||||
return gui.Errors.ErrSubProcess
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
options := []*gitFlowOption{
|
||||
{
|
||||
// not localising here because it's one to one with the actual git flow commands
|
||||
description: fmt.Sprintf("finish branch '%s'", branch.Name),
|
||||
handler: func() error {
|
||||
return gui.gitFlowFinishBranch(gitFlowConfig, branch.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "start feature",
|
||||
handler: startHandler("feature"),
|
||||
},
|
||||
{
|
||||
description: "start hotfix",
|
||||
handler: startHandler("hotfix"),
|
||||
},
|
||||
{
|
||||
description: "start release",
|
||||
handler: startHandler("release"),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("git flow", options, len(options), handleMenuPress)
|
||||
}
|
||||
301
pkg/gui/gui.go
301
pkg/gui/gui.go
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
// "io"
|
||||
@@ -24,9 +25,11 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -66,19 +69,22 @@ type Teml i18n.Teml
|
||||
|
||||
// Gui wraps the gocui Gui object which handles rendering and events
|
||||
type Gui struct {
|
||||
g *gocui.Gui
|
||||
Log *logrus.Entry
|
||||
GitCommand *commands.GitCommand
|
||||
OSCommand *commands.OSCommand
|
||||
SubProcess *exec.Cmd
|
||||
State guiState
|
||||
Config config.AppConfigurer
|
||||
Tr *i18n.Localizer
|
||||
Errors SentinelErrors
|
||||
Updater *updates.Updater
|
||||
statusManager *statusManager
|
||||
credentials credentials
|
||||
waitForIntro sync.WaitGroup
|
||||
g *gocui.Gui
|
||||
Log *logrus.Entry
|
||||
GitCommand *commands.GitCommand
|
||||
OSCommand *commands.OSCommand
|
||||
SubProcess *exec.Cmd
|
||||
State guiState
|
||||
Config config.AppConfigurer
|
||||
Tr *i18n.Localizer
|
||||
Errors SentinelErrors
|
||||
Updater *updates.Updater
|
||||
statusManager *statusManager
|
||||
credentials credentials
|
||||
waitForIntro sync.WaitGroup
|
||||
fileWatcher *fileWatcher
|
||||
viewBufferManagerMap map[string]*tasks.ViewBufferManager
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
// for now the staging panel state, unlike the other panel states, is going to be
|
||||
@@ -105,13 +111,31 @@ type filePanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
// TODO: consider splitting this out into the window and the branches view
|
||||
type branchPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type remotePanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type remoteBranchesState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type tagsPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type commitPanelState struct {
|
||||
SelectedLine int
|
||||
SpecificDiffMode bool
|
||||
LimitCommits bool
|
||||
}
|
||||
|
||||
type reflogCommitPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type stashPanelState struct {
|
||||
@@ -133,34 +157,44 @@ type statusPanelState struct {
|
||||
}
|
||||
|
||||
type panelStates struct {
|
||||
Files *filePanelState
|
||||
Branches *branchPanelState
|
||||
Commits *commitPanelState
|
||||
Stash *stashPanelState
|
||||
Menu *menuPanelState
|
||||
LineByLine *lineByLinePanelState
|
||||
Merging *mergingPanelState
|
||||
CommitFiles *commitFilesPanelState
|
||||
Status *statusPanelState
|
||||
Files *filePanelState
|
||||
Branches *branchPanelState
|
||||
Remotes *remotePanelState
|
||||
RemoteBranches *remoteBranchesState
|
||||
Tags *tagsPanelState
|
||||
Commits *commitPanelState
|
||||
ReflogCommits *reflogCommitPanelState
|
||||
Stash *stashPanelState
|
||||
Menu *menuPanelState
|
||||
LineByLine *lineByLinePanelState
|
||||
Merging *mergingPanelState
|
||||
CommitFiles *commitFilesPanelState
|
||||
Status *statusPanelState
|
||||
}
|
||||
|
||||
type guiState struct {
|
||||
Files []*commands.File
|
||||
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
|
||||
Panels *panelStates
|
||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||
Context string // important not to set this value directly but to use gui.changeContext("new context")
|
||||
CherryPickedCommits []*commands.Commit
|
||||
SplitMainPanel bool
|
||||
RetainOriginalDir bool
|
||||
Files []*commands.File
|
||||
Branches []*commands.Branch
|
||||
Commits []*commands.Commit
|
||||
StashEntries []*commands.StashEntry
|
||||
CommitFiles []*commands.CommitFile
|
||||
ReflogCommits []*commands.Commit
|
||||
DiffEntries []*commands.Commit
|
||||
Remotes []*commands.Remote
|
||||
RemoteBranches []*commands.RemoteBranch
|
||||
Tags []*commands.Tag
|
||||
MenuItemCount int // can't store the actual list because it's of interface{} type
|
||||
PreviousView string
|
||||
Platform commands.Platform
|
||||
Updating bool
|
||||
Panels *panelStates
|
||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||
MainContext string // used to keep the main and secondary views' contexts in sync
|
||||
CherryPickedCommits []*commands.Commit
|
||||
SplitMainPanel bool
|
||||
RetainOriginalDir bool
|
||||
IsRefreshingFiles bool
|
||||
RefreshingFilesMutex sync.Mutex
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
@@ -177,12 +211,16 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
DiffEntries: make([]*commands.Commit, 0),
|
||||
Platform: *oSCommand.Platform,
|
||||
Panels: &panelStates{
|
||||
Files: &filePanelState{SelectedLine: -1},
|
||||
Branches: &branchPanelState{SelectedLine: 0},
|
||||
Commits: &commitPanelState{SelectedLine: -1},
|
||||
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
||||
Stash: &stashPanelState{SelectedLine: -1},
|
||||
Menu: &menuPanelState{SelectedLine: 0},
|
||||
Files: &filePanelState{SelectedLine: -1},
|
||||
Branches: &branchPanelState{SelectedLine: 0},
|
||||
Remotes: &remotePanelState{SelectedLine: 0},
|
||||
RemoteBranches: &remoteBranchesState{SelectedLine: -1},
|
||||
Tags: &tagsPanelState{SelectedLine: -1},
|
||||
Commits: &commitPanelState{SelectedLine: -1, LimitCommits: true},
|
||||
ReflogCommits: &reflogCommitPanelState{SelectedLine: 0}, // TODO: might need to make -1
|
||||
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
||||
Stash: &stashPanelState{SelectedLine: -1},
|
||||
Menu: &menuPanelState{SelectedLine: 0},
|
||||
Merging: &mergingPanelState{
|
||||
ConflictIndex: 0,
|
||||
ConflictTop: true,
|
||||
@@ -194,16 +232,19 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: oSCommand,
|
||||
State: initialState,
|
||||
Config: config,
|
||||
Tr: tr,
|
||||
Updater: updater,
|
||||
statusManager: &statusManager{},
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: oSCommand,
|
||||
State: initialState,
|
||||
Config: config,
|
||||
Tr: tr,
|
||||
Updater: updater,
|
||||
statusManager: &statusManager{},
|
||||
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
|
||||
}
|
||||
|
||||
gui.watchFilesForChanges()
|
||||
|
||||
gui.GenerateSentinelErrors()
|
||||
|
||||
return gui, nil
|
||||
@@ -224,8 +265,14 @@ func (gui *Gui) scrollDownView(viewName string) error {
|
||||
_, sy := mainView.Size()
|
||||
y += sy
|
||||
}
|
||||
if y < len(mainView.BufferLines()) {
|
||||
return mainView.SetOrigin(ox, oy+gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))
|
||||
scrollHeight := gui.Config.GetUserConfig().GetInt("gui.scrollHeight")
|
||||
if y < mainView.LinesHeight() {
|
||||
if err := mainView.SetOrigin(ox, oy+scrollHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if manager, ok := gui.viewBufferManagerMap[viewName]; ok {
|
||||
manager.ReadLines(scrollHeight)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -293,18 +340,18 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
||||
}
|
||||
switch v.Name() {
|
||||
case "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
|
||||
if v.Context == "local-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
|
||||
}
|
||||
}
|
||||
case "main":
|
||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||
if err := gui.changeContext("normal"); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.changeMainViewsContext("normal")
|
||||
case "commitFiles":
|
||||
if gui.State.Context != "patch-building" {
|
||||
if gui.State.MainContext != "patch-building" {
|
||||
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -412,10 +459,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
leftSideWidth := width / 3
|
||||
panelSplitX := width - 1
|
||||
mainPanelRight := width - 1
|
||||
secondaryPanelLeft := width - 1
|
||||
secondaryPanelTop := 0
|
||||
mainPanelBottom := height - 2
|
||||
if gui.State.SplitMainPanel {
|
||||
units := 7
|
||||
leftSideWidth = width / units
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
if width < 220 {
|
||||
mainPanelBottom = height/2 - 1
|
||||
secondaryPanelTop = mainPanelBottom + 1
|
||||
secondaryPanelLeft = leftSideWidth + 1
|
||||
} else {
|
||||
units := 5
|
||||
leftSideWidth = width / units
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
}
|
||||
}
|
||||
|
||||
main := "main"
|
||||
@@ -426,7 +485,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
secondary = "main"
|
||||
}
|
||||
|
||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, panelSplitX, height-2, gocui.LEFT)
|
||||
// reading more lines into main view buffers upon resize
|
||||
prevMainView, err := gui.g.View("main")
|
||||
if err == nil {
|
||||
_, prevMainHeight := prevMainView.Size()
|
||||
heightDiff := mainPanelBottom - prevMainHeight
|
||||
if heightDiff > 0 {
|
||||
if manager, ok := gui.viewBufferManagerMap["main"]; ok {
|
||||
manager.ReadLines(heightDiff)
|
||||
}
|
||||
if manager, ok := gui.viewBufferManagerMap["secondary"]; ok {
|
||||
manager.ReadLines(heightDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -440,7 +514,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if !gui.State.SplitMainPanel {
|
||||
hiddenViewOffset = 9999
|
||||
}
|
||||
secondaryView, err := g.SetView(secondary, panelSplitX+1+hiddenViewOffset, hiddenViewOffset, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenViewOffset, hiddenViewOffset+secondaryPanelTop, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -474,6 +548,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||
branchesView.Tabs = []string{"Local Branches", "Remotes", "Tags"}
|
||||
branchesView.FgColor = textColor
|
||||
}
|
||||
|
||||
@@ -491,6 +566,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
commitsView.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
commitsView.Tabs = []string{"Commits", "Reflog"}
|
||||
commitsView.FgColor = textColor
|
||||
}
|
||||
|
||||
@@ -514,7 +590,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", width, height, width*2, height*2, 0); err != nil {
|
||||
if commitMessageView, err := g.SetView("commitMessage", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
@@ -522,12 +598,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
||||
commitMessageView.FgColor = textColor
|
||||
commitMessageView.Editable = true
|
||||
commitMessageView.Editor = gocui.EditorFunc(gui.commitMessageEditor)
|
||||
}
|
||||
}
|
||||
|
||||
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", width, height, width*2, height*2, 0); err != nil {
|
||||
if credentialsView, err := g.SetView("credentials", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
@@ -565,7 +642,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
// doing this here because it'll only happen once
|
||||
if err := gui.loadNewRepo(); err != nil {
|
||||
if err := gui.onInitialViewsCreation(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -583,22 +660,31 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
type listViewState struct {
|
||||
selectedLine int
|
||||
lineCount int
|
||||
view *gocui.View
|
||||
context string
|
||||
}
|
||||
|
||||
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)},
|
||||
listViews := []listViewState{
|
||||
{view: filesView, context: "", selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
|
||||
{view: branchesView, context: "local-branches", selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
|
||||
{view: branchesView, context: "remotes", selectedLine: gui.State.Panels.Remotes.SelectedLine, lineCount: len(gui.State.Remotes)},
|
||||
{view: branchesView, context: "remote-branches", selectedLine: gui.State.Panels.RemoteBranches.SelectedLine, lineCount: len(gui.State.Remotes)},
|
||||
{view: commitsView, context: "branch-commits", selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
|
||||
{view: commitsView, context: "reflog-commits", selectedLine: gui.State.Panels.ReflogCommits.SelectedLine, lineCount: len(gui.State.ReflogCommits)},
|
||||
{view: stashView, context: "", 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] = listViewState{selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}
|
||||
listViews = append(listViews, listViewState{view: menuView, context: "", selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount})
|
||||
}
|
||||
for view, state := range listViews {
|
||||
for _, listView := range listViews {
|
||||
// ignore views where the context doesn't match up with the selected line we're trying to focus
|
||||
if listView.context != "" && (listView.view.Context != listView.context) {
|
||||
continue
|
||||
}
|
||||
// check if the selected line is now out of view and if so refocus it
|
||||
if err := gui.focusPoint(0, state.selectedLine, state.lineCount, view); err != nil {
|
||||
if err := gui.focusPoint(0, listView.selectedLine, listView.lineCount, listView.view); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -610,6 +696,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return gui.resizeCurrentPopupPanel(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) onInitialViewsCreation() error {
|
||||
gui.changeMainViewsContext("normal")
|
||||
|
||||
gui.getBranchesView().Context = "local-branches"
|
||||
gui.getCommitsView().Context = "branch-commits"
|
||||
|
||||
return gui.loadNewRepo()
|
||||
}
|
||||
|
||||
func (gui *Gui) loadNewRepo() error {
|
||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||
if err := gui.updateRecentRepoList(); err != nil {
|
||||
@@ -681,28 +776,27 @@ func (gui *Gui) fetch(g *gocui.Gui, v *gocui.View, canAskForCredentials bool) (u
|
||||
return unamePassOpend, err
|
||||
}
|
||||
|
||||
func (gui *Gui) renderAppStatus() error {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
if appStatus != "" {
|
||||
return gui.renderString(gui.g, "appStatus", appStatus)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderGlobalOptions() error {
|
||||
return gui.renderOptionsMap(map[string]string{
|
||||
"PgUp/PgDn": gui.Tr.SLocalize("scroll"),
|
||||
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
|
||||
"esc/q": gui.Tr.SLocalize("close"),
|
||||
"x": gui.Tr.SLocalize("menu"),
|
||||
"1-5": gui.Tr.SLocalize("jump"),
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.scrollUpMain"), gui.getKeyDisplay("universal.scrollDownMain")): gui.Tr.SLocalize("scroll"),
|
||||
fmt.Sprintf("%s %s %s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock"), gui.getKeyDisplay("universal.prevItem"), gui.getKeyDisplay("universal.nextItem")): gui.Tr.SLocalize("navigate"),
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.return"), gui.getKeyDisplay("universal.quit")): gui.Tr.SLocalize("close"),
|
||||
fmt.Sprintf("%s", gui.getKeyDisplay("universal.optionMenu")): gui.Tr.SLocalize("menu"),
|
||||
"1-5": gui.Tr.SLocalize("jump"),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
|
||||
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
go func() {
|
||||
for range time.Tick(interval) {
|
||||
_ = function()
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_ = function()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -717,7 +811,7 @@ func (gui *Gui) startBackgroundFetch() {
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("NoAutomaticGitFetchTitle"), gui.Tr.SLocalize("NoAutomaticGitFetchBody"), nil, nil)
|
||||
} else {
|
||||
gui.goEvery(time.Second*60, func() error {
|
||||
gui.goEvery(time.Second*60, gui.stopChan, func() error {
|
||||
_, err := gui.fetch(gui.g, gui.g.CurrentView(), false)
|
||||
return err
|
||||
})
|
||||
@@ -731,6 +825,9 @@ func (gui *Gui) Run() error {
|
||||
return err
|
||||
}
|
||||
defer g.Close()
|
||||
gui.stopChan = make(chan struct{})
|
||||
|
||||
g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
|
||||
|
||||
if gui.Config.GetUserConfig().GetBool("gui.mouseEvents") {
|
||||
g.Mouse = true
|
||||
@@ -758,8 +855,7 @@ func (gui *Gui) Run() error {
|
||||
go gui.startBackgroundFetch()
|
||||
}
|
||||
|
||||
gui.goEvery(time.Second*10, gui.refreshFiles)
|
||||
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
|
||||
gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFiles)
|
||||
|
||||
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
||||
|
||||
@@ -779,6 +875,17 @@ func (gui *Gui) Run() error {
|
||||
func (gui *Gui) RunWithSubprocesses() error {
|
||||
for {
|
||||
if err := gui.Run(); err != nil {
|
||||
for _, manager := range gui.viewBufferManagerMap {
|
||||
manager.Close()
|
||||
}
|
||||
gui.viewBufferManagerMap = map[string]*tasks.ViewBufferManager{}
|
||||
|
||||
if !gui.fileWatcher.Disabled {
|
||||
gui.fileWatcher.Watcher.Close()
|
||||
}
|
||||
|
||||
close(gui.stopChan)
|
||||
|
||||
if err == gocui.ErrQuit {
|
||||
if !gui.State.RetainOriginalDir {
|
||||
if err := gui.recordCurrentDirectory(); err != nil {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -226,7 +226,7 @@ func (gui *Gui) refreshMainView() error {
|
||||
var includedLineIndices []int
|
||||
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
||||
// how to get around this
|
||||
if gui.State.Context == "patch-building" {
|
||||
if gui.State.MainContext == "patch-building" {
|
||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
}
|
||||
@@ -314,3 +314,8 @@ func (gui *Gui) handleToggleSelectHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
return gui.focusSelection(state.SelectMode == HUNK)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapeLineByLinePanel() {
|
||||
gui.changeMainViewsContext("normal")
|
||||
gui.State.Panels.LineByLine = nil
|
||||
}
|
||||
|
||||
178
pkg/gui/list_view.go
Normal file
178
pkg/gui/list_view.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
type listView struct {
|
||||
viewName string
|
||||
context string
|
||||
getItemsLength func() int
|
||||
getSelectedLineIdxPtr func() *int
|
||||
handleFocus func(g *gocui.Gui, v *gocui.View) error
|
||||
handleItemSelect func(g *gocui.Gui, v *gocui.View) error
|
||||
handleClickSelectedItem func(g *gocui.Gui, v *gocui.View) error
|
||||
gui *Gui
|
||||
rendersToMainView bool
|
||||
}
|
||||
|
||||
func (lv *listView) handlePrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
return lv.handleLineChange(-1)
|
||||
}
|
||||
|
||||
func (lv *listView) handleNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
return lv.handleLineChange(1)
|
||||
}
|
||||
|
||||
func (lv *listView) handleLineChange(change int) error {
|
||||
if !lv.gui.isPopupPanel(lv.viewName) && lv.gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
lv.gui.changeSelectedLine(lv.getSelectedLineIdxPtr(), lv.getItemsLength(), change)
|
||||
|
||||
if lv.rendersToMainView {
|
||||
if err := lv.gui.resetOrigin(lv.gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
view, err := lv.gui.g.View(lv.viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return lv.handleItemSelect(lv.gui.g, view)
|
||||
}
|
||||
|
||||
func (lv *listView) handleClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if !lv.gui.isPopupPanel(lv.viewName) && lv.gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
selectedLineIdxPtr := lv.getSelectedLineIdxPtr()
|
||||
prevSelectedLineIdx := *selectedLineIdxPtr
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLineIdx > lv.getItemsLength()-1 {
|
||||
return lv.handleFocus(lv.gui.g, v)
|
||||
}
|
||||
|
||||
*selectedLineIdxPtr = newSelectedLineIdx
|
||||
|
||||
if lv.rendersToMainView {
|
||||
if err := lv.gui.resetOrigin(lv.gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
prevViewName := lv.gui.currentViewName()
|
||||
if prevSelectedLineIdx == newSelectedLineIdx && prevViewName == lv.viewName && lv.handleClickSelectedItem != nil {
|
||||
return lv.handleClickSelectedItem(lv.gui.g, v)
|
||||
}
|
||||
return lv.handleItemSelect(lv.gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) getListViews() []*listView {
|
||||
return []*listView{
|
||||
{
|
||||
viewName: "menu",
|
||||
getItemsLength: func() int { return gui.getMenuView().LinesHeight() },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Menu.SelectedLine },
|
||||
handleFocus: gui.handleMenuSelect,
|
||||
handleItemSelect: gui.handleMenuSelect,
|
||||
// need to add a layer of indirection here because the callback changes during runtime
|
||||
handleClickSelectedItem: gui.wrappedHandler(func() error { return gui.State.Panels.Menu.OnPress(gui.g, nil) }),
|
||||
gui: gui,
|
||||
rendersToMainView: false,
|
||||
},
|
||||
{
|
||||
viewName: "files",
|
||||
getItemsLength: func() int { return len(gui.State.Files) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Files.SelectedLine },
|
||||
handleFocus: gui.focusAndSelectFile,
|
||||
handleItemSelect: gui.focusAndSelectFile,
|
||||
handleClickSelectedItem: gui.handleFilePress,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "branches",
|
||||
context: "local-branches",
|
||||
getItemsLength: func() int { return len(gui.State.Branches) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Branches.SelectedLine },
|
||||
handleFocus: gui.handleBranchSelect,
|
||||
handleItemSelect: gui.handleBranchSelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "branches",
|
||||
context: "remotes",
|
||||
getItemsLength: func() int { return len(gui.State.Remotes) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Remotes.SelectedLine },
|
||||
handleFocus: gui.wrappedHandler(gui.renderRemotesWithSelection),
|
||||
handleItemSelect: gui.handleRemoteSelect,
|
||||
handleClickSelectedItem: gui.handleRemoteEnter,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "branches",
|
||||
context: "remote-branches",
|
||||
getItemsLength: func() int { return len(gui.State.RemoteBranches) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.RemoteBranches.SelectedLine },
|
||||
handleFocus: gui.handleRemoteBranchSelect,
|
||||
handleItemSelect: gui.handleRemoteBranchSelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "branches",
|
||||
context: "tags",
|
||||
getItemsLength: func() int { return len(gui.State.Tags) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Tags.SelectedLine },
|
||||
handleFocus: gui.handleTagSelect,
|
||||
handleItemSelect: gui.handleTagSelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
|
||||
{
|
||||
viewName: "commits",
|
||||
context: "branch-commits",
|
||||
getItemsLength: func() int { return len(gui.State.Commits) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Commits.SelectedLine },
|
||||
handleFocus: gui.handleCommitSelect,
|
||||
handleItemSelect: gui.handleCommitSelect,
|
||||
handleClickSelectedItem: gui.handleSwitchToCommitFilesPanel,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "commits",
|
||||
context: "reflog-commits",
|
||||
getItemsLength: func() int { return len(gui.State.ReflogCommits) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.ReflogCommits.SelectedLine },
|
||||
handleFocus: gui.handleReflogCommitSelect,
|
||||
handleItemSelect: gui.handleReflogCommitSelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "stash",
|
||||
getItemsLength: func() int { return len(gui.State.StashEntries) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.Stash.SelectedLine },
|
||||
handleFocus: gui.handleStashEntrySelect,
|
||||
handleItemSelect: gui.handleStashEntrySelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
{
|
||||
viewName: "commitFiles",
|
||||
getItemsLength: func() int { return len(gui.State.CommitFiles) },
|
||||
getSelectedLineIdxPtr: func() *int { return &gui.State.Panels.CommitFiles.SelectedLine },
|
||||
handleFocus: gui.handleCommitFileSelect,
|
||||
handleItemSelect: gui.handleCommitFileSelect,
|
||||
gui: gui,
|
||||
rendersToMainView: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -14,27 +14,13 @@ func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.Menu
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), false)
|
||||
|
||||
return gui.handleMenuSelect(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.Menu
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true)
|
||||
|
||||
return gui.handleMenuSelect(g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) renderMenuOptions() error {
|
||||
optionsMap := map[string]string{
|
||||
"esc/q": gui.Tr.SLocalize("close"),
|
||||
"↑ ↓": gui.Tr.SLocalize("navigate"),
|
||||
"space": gui.Tr.SLocalize("execute"),
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.return"), gui.getKeyDisplay("universal.quit")): gui.Tr.SLocalize("close"),
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay("universal.prevItem"), gui.getKeyDisplay("universal.nextItem")): gui.Tr.SLocalize("navigate"),
|
||||
gui.getKeyDisplay("universal.select"): gui.Tr.SLocalize("execute"),
|
||||
}
|
||||
return gui.renderOptionsMap(optionsMap)
|
||||
}
|
||||
@@ -87,7 +73,7 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
|
||||
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
|
||||
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
|
||||
|
||||
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||
if err := gui.g.SetKeybinding("menu", nil, key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -103,15 +89,3 @@ func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handl
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuClick(g *gocui.Gui, v *gocui.View) error {
|
||||
itemCount := gui.State.MenuItemCount
|
||||
handleSelect := gui.handleMenuSelect
|
||||
selectedLine := &gui.State.Panels.Menu.SelectedLine
|
||||
|
||||
if err := gui.handleClick(v, itemCount, selectedLine, handleSelect); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Panels.Menu.OnPress(g, v)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package gui
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
@@ -211,15 +212,16 @@ func (gui *Gui) refreshMergePanel() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.renderString(gui.g, "main", content); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.scrollToConflict(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainView := gui.getMainView()
|
||||
mainView.Wrap = false
|
||||
if err := gui.setViewContent(gui.g, mainView, content); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.Log.Warn("scrolling to conflict")
|
||||
if err := gui.scrollToConflict(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -243,11 +245,11 @@ func (gui *Gui) scrollToConflict(g *gocui.Gui) error {
|
||||
|
||||
func (gui *Gui) renderMergeOptions() error {
|
||||
return gui.renderOptionsMap(map[string]string{
|
||||
"↑ ↓": gui.Tr.SLocalize("selectHunk"),
|
||||
"← →": gui.Tr.SLocalize("navigateConflicts"),
|
||||
"space": gui.Tr.SLocalize("pickHunk"),
|
||||
"b": gui.Tr.SLocalize("pickBothHunks"),
|
||||
"z": gui.Tr.SLocalize("undo"),
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay("universal.prevItem"), gui.getKeyDisplay("universal.nextItem")): gui.Tr.SLocalize("selectHunk"),
|
||||
fmt.Sprintf("%s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock")): gui.Tr.SLocalize("navigateConflicts"),
|
||||
gui.getKeyDisplay("universal.select"): gui.Tr.SLocalize("pickHunk"),
|
||||
gui.getKeyDisplay("main.pickBothHunks"): gui.Tr.SLocalize("pickBothHunks"),
|
||||
gui.getKeyDisplay("main.undo"): gui.Tr.SLocalize("undo"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
@@ -13,15 +14,17 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
bindingsGlobal, bindingsPanel []*Binding
|
||||
)
|
||||
|
||||
bindings := gui.GetCurrentKeybindings()
|
||||
bindings := gui.GetInitialKeybindings()
|
||||
|
||||
for _, binding := range bindings {
|
||||
if binding.GetKey() != "" && binding.Description != "" {
|
||||
if GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
|
||||
switch binding.ViewName {
|
||||
case "":
|
||||
bindingsGlobal = append(bindingsGlobal, binding)
|
||||
case v.Name():
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
if len(binding.Contexts) == 0 || utils.IncludesString(binding.Contexts, v.Context) {
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,7 @@ func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) erro
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapePatchBuildingPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Panels.LineByLine = nil
|
||||
gui.changeContext("normal")
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
|
||||
if gui.GitCommand.PatchManager.IsEmpty() {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
|
||||
@@ -67,7 +67,7 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||
}
|
||||
|
||||
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
|
||||
if gui.State.Context == "patch-building" {
|
||||
if gui.State.MainContext == "patch-building" {
|
||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||
}
|
||||
return nil
|
||||
|
||||
97
pkg/gui/reflog_panel.go
Normal file
97
pkg/gui/reflog_panel.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedReflogCommit() *commands.Commit {
|
||||
selectedLine := gui.State.Panels.ReflogCommits.SelectedLine
|
||||
if selectedLine == -1 || len(gui.State.ReflogCommits) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.ReflogCommits[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Reflog Entry"
|
||||
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
if commit == nil {
|
||||
return gui.newStringTask("main", "No reflog history")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshReflogCommits() error {
|
||||
commits, err := gui.GitCommand.GetReflogCommits()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.State.ReflogCommits = commits
|
||||
|
||||
if gui.getCommitsView().Context == "reflog-commits" {
|
||||
return gui.renderReflogCommitsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderReflogCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits))
|
||||
if err := gui.renderListPanel(commitsView, gui.State.ReflogCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" {
|
||||
if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutReflogCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := gui.createConfirmationPanel(g, gui.getCommitsView(), true, gui.Tr.SLocalize("checkoutCommit"), gui.Tr.SLocalize("SureCheckoutThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutRef(commit.Sha)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
60
pkg/gui/reflog_reset_options_panel.go
Normal file
60
pkg/gui/reflog_reset_options_panel.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type reflogResetOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *reflogResetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
|
||||
resetFunction := func(reset func(string) error) func() error {
|
||||
return func() error {
|
||||
if err := reset(commit.Sha); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = 0
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}
|
||||
}
|
||||
|
||||
options := []*reflogResetOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardReset"),
|
||||
command: fmt.Sprintf("reset --hard %s", commit.Sha),
|
||||
handler: resetFunction(gui.GitCommand.ResetHard),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("softReset"),
|
||||
command: fmt.Sprintf("reset --soft %s", commit.Sha),
|
||||
handler: resetFunction(gui.GitCommand.ResetSoft),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
}
|
||||
134
pkg/gui/remote_branches_panel.go
Normal file
134
pkg/gui/remote_branches_panel.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedRemoteBranch() *commands.RemoteBranch {
|
||||
selectedLine := gui.State.Panels.RemoteBranches.SelectedLine
|
||||
if selectedLine == -1 || len(gui.State.RemoteBranches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.RemoteBranches[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoteBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Remote Branch"
|
||||
|
||||
remote := gui.getSelectedRemote()
|
||||
remoteBranch := gui.getSelectedRemoteBranch()
|
||||
if remoteBranch == nil {
|
||||
return gui.newStringTask("main", "No branches for this remote")
|
||||
}
|
||||
|
||||
gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||
if err := gui.focusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
branchName := fmt.Sprintf("%s/%s", remote.Name, remoteBranch.Name)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(branchName),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoteBranchesEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.switchBranchesPanelContext("remotes")
|
||||
}
|
||||
|
||||
func (gui *Gui) renderRemoteBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remote-branches" {
|
||||
if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
remoteBranch := gui.getSelectedRemoteBranch()
|
||||
if remoteBranch == nil {
|
||||
return nil
|
||||
}
|
||||
if err := gui.handleCheckoutRef(remoteBranch.RemoteName + "/" + remoteBranch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchBranchesPanelContext("local-branches")
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMergeRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranchName := gui.getSelectedRemoteBranch().Name
|
||||
return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDeleteRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
remoteBranch := gui.getSelectedRemoteBranch()
|
||||
if remoteBranch == nil {
|
||||
return nil
|
||||
}
|
||||
message := fmt.Sprintf("%s '%s/%s'?", gui.Tr.SLocalize("DeleteRemoteBranchMessage"), remoteBranch.RemoteName, remoteBranch.Name)
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("DeleteRemoteBranch"), message, func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("DeletingStatus"), func() error {
|
||||
if err := gui.GitCommand.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshRemotes()
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRebaseOntoRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranchName := gui.getSelectedRemoteBranch().Name
|
||||
return gui.handleRebaseOntoBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSetBranchUpstream(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranch := gui.getSelectedRemoteBranch()
|
||||
checkedOutBranch := gui.getCheckedOutBranch()
|
||||
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"SetUpstreamMessage",
|
||||
Teml{
|
||||
"checkedOut": checkedOutBranch.Name,
|
||||
"selected": selectedBranch.RemoteName + "/" + selectedBranch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("SetUpstreamTitle"), message, func(*gocui.Gui, *gocui.View) error {
|
||||
if err := gui.GitCommand.SetBranchUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}, nil)
|
||||
}
|
||||
195
pkg/gui/remotes_panel.go
Normal file
195
pkg/gui/remotes_panel.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedRemote() *commands.Remote {
|
||||
selectedLine := gui.State.Panels.Remotes.SelectedLine
|
||||
if selectedLine == -1 || len(gui.State.Remotes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Remotes[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoteSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Remote"
|
||||
|
||||
remote := gui.getSelectedRemote()
|
||||
if remote == nil {
|
||||
return gui.newStringTask("main", "No remotes")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.newStringTask("main", fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n")))
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshRemotes() error {
|
||||
prevSelectedRemote := gui.getSelectedRemote()
|
||||
|
||||
remotes, err := gui.GitCommand.GetRemotes()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.State.Remotes = remotes
|
||||
|
||||
// we need to ensure our selected remote branches aren't now outdated
|
||||
if prevSelectedRemote != nil && gui.State.RemoteBranches != nil {
|
||||
// find remote now
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == prevSelectedRemote.Name {
|
||||
gui.State.RemoteBranches = remote.Branches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: see if this works for deleting remote branches
|
||||
switch gui.getBranchesView().Context {
|
||||
case "remotes":
|
||||
return gui.renderRemotesWithSelection()
|
||||
case "remote-branches":
|
||||
return gui.renderRemoteBranchesWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderRemotesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remotes" {
|
||||
if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoteEnter(g *gocui.Gui, v *gocui.View) error {
|
||||
// naive implementation: get the branches and render them to the list, change the context
|
||||
remote := gui.getSelectedRemote()
|
||||
if remote == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.RemoteBranches = remote.Branches
|
||||
|
||||
newSelectedLine := 0
|
||||
if len(remote.Branches) == 0 {
|
||||
newSelectedLine = -1
|
||||
}
|
||||
gui.State.Panels.RemoteBranches.SelectedLine = newSelectedLine
|
||||
|
||||
return gui.switchBranchesPanelContext("remote-branches")
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAddRemote(g *gocui.Gui, v *gocui.View) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
return gui.createPromptPanel(g, branchesView, gui.Tr.SLocalize("newRemoteName"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
remoteName := gui.trimmedContent(v)
|
||||
return gui.createPromptPanel(g, branchesView, gui.Tr.SLocalize("newRemoteUrl"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
remoteUrl := gui.trimmedContent(v)
|
||||
if err := gui.GitCommand.AddRemote(remoteName, remoteUrl); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshRemotes()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoveRemote(g *gocui.Gui, v *gocui.View) error {
|
||||
remote := gui.getSelectedRemote()
|
||||
if remote == nil {
|
||||
return nil
|
||||
}
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("removeRemote"), gui.Tr.SLocalize("removeRemotePrompt")+" '"+remote.Name+"'?", func(*gocui.Gui, *gocui.View) error {
|
||||
if err := gui.GitCommand.RemoveRemote(remote.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshRemotes()
|
||||
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEditRemote(g *gocui.Gui, v *gocui.View) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
remote := gui.getSelectedRemote()
|
||||
if remote == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
editNameMessage := gui.Tr.TemplateLocalize(
|
||||
"editRemoteName",
|
||||
Teml{
|
||||
"remoteName": remote.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.createPromptPanel(g, branchesView, editNameMessage, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
updatedRemoteName := gui.trimmedContent(v)
|
||||
|
||||
if updatedRemoteName != remote.Name {
|
||||
if err := gui.GitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
editUrlMessage := gui.Tr.TemplateLocalize(
|
||||
"editRemoteUrl",
|
||||
Teml{
|
||||
"remoteName": updatedRemoteName,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.createPromptPanel(g, branchesView, editUrlMessage, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
updatedRemoteUrl := gui.trimmedContent(v)
|
||||
if err := gui.GitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshRemotes()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFetchRemote(g *gocui.Gui, v *gocui.View) error {
|
||||
remote := gui.getSelectedRemote()
|
||||
if remote == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("FetchingRemoteStatus"), func() error {
|
||||
if err := gui.GitCommand.FetchRemote(remote.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshRemotes()
|
||||
})
|
||||
}
|
||||
@@ -12,6 +12,13 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx
|
||||
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
// We need to force focus here because the confirmation panel for safely staging lines does not return focus automatically.
|
||||
// This is because if we tell it to return focus it will unconditionally return it to the main panel which may not be what we want
|
||||
// e.g. in the event that there's nothing left to stage.
|
||||
if err := gui.switchFocus(gui.g, nil, gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
@@ -87,26 +94,36 @@ func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Panels.LineByLine = nil
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
|
||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelection(false)
|
||||
return gui.applySelectionWithPrompt(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelection(true)
|
||||
return gui.applySelectionWithPrompt(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelection(reverse bool) error {
|
||||
func (gui *Gui) applySelectionWithPrompt(reverse bool) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if !reverse && state.SecondaryFocused {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||
} else if reverse && !state.SecondaryFocused && !gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to unstage these lines? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.applySelection(reverse)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return gui.applySelection(reverse)
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelection(reverse bool) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -34,16 +34,19 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
stashEntry := gui.getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries), v); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
// doing this asynchronously cos it can take time
|
||||
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
|
||||
_ = gui.renderString(g, "main", diff)
|
||||
}()
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -71,34 +74,6 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Stash
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleStashEntrySelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Stash
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleStashEntrySelect(gui.g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -34,7 +35,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
|
||||
if len(branches) > 0 {
|
||||
branch := branches[0]
|
||||
name := utils.ColoredString(branch.Name, branch.GetColor())
|
||||
name := utils.ColoredString(branch.Name, commands.GetBranchColor(branch.Name))
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||
}
|
||||
@@ -110,7 +111,7 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
magenta.Sprint("Become a sponsor (github is matching all donations for 12 months): https://github.com/sponsors/jesseduffield"), // caffeine ain't free
|
||||
}, "\n\n")
|
||||
|
||||
return gui.renderString(g, "main", dashboardString)
|
||||
return gui.newStringTask("main", dashboardString)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
142
pkg/gui/tags_panel.go
Normal file
142
pkg/gui/tags_panel.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedTag() *commands.Tag {
|
||||
selectedLine := gui.State.Panels.Tags.SelectedLine
|
||||
if selectedLine == -1 || len(gui.State.Tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Tags[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTagSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Tag"
|
||||
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return gui.newStringTask("main", "No tags")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(tag.Name),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshTags() error {
|
||||
tags, err := gui.GitCommand.GetTags()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
gui.State.Tags = tags
|
||||
|
||||
if gui.getBranchesView().Context == "tags" {
|
||||
gui.renderTagsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderTagsWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
||||
if err := gui.renderListPanel(branchesView, gui.State.Tags); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "tags" {
|
||||
if err := gui.handleTagSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutTag(g *gocui.Gui, v *gocui.View) error {
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
if err := gui.handleCheckoutRef(tag.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchBranchesPanelContext("local-branches")
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDeleteTag(g *gocui.Gui, v *gocui.View) error {
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"DeleteTagPrompt",
|
||||
Teml{
|
||||
"tagName": tag.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("DeleteTagTitle"), prompt, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshTags()
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePushTag(g *gocui.Gui, v *gocui.View) error {
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
title := gui.Tr.TemplateLocalize(
|
||||
"PushTagTitle",
|
||||
Teml{
|
||||
"tagName": tag.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.createPromptPanel(gui.g, v, title, "origin", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.PushTag(v.Buffer(), tag.Name); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshTags()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateTag(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createPromptPanel(gui.g, v, gui.Tr.SLocalize("CreateTagTitle"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
// leaving commit SHA blank so that we're just creating the tag for the current commit
|
||||
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), ""); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.refreshTags()
|
||||
})
|
||||
}
|
||||
78
pkg/gui/tasks_adapter.go
Normal file
78
pkg/gui/tasks_adapter.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
)
|
||||
|
||||
func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error {
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil // swallowing for now
|
||||
}
|
||||
|
||||
_, height := view.Size()
|
||||
_, oy := view.Origin()
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(cmd, height+oy+10)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newTask(viewName string, f func(chan struct{}) error) error {
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil // swallowing for now
|
||||
}
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
if err := manager.NewTask(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newStringTask(viewName string, str string) error {
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil // swallowing for now
|
||||
}
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
return gui.renderString(gui.g, viewName, str)
|
||||
}
|
||||
|
||||
if err := manager.NewTask(f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
manager, ok := gui.viewBufferManagerMap[view.Name()]
|
||||
if !ok {
|
||||
manager = tasks.NewViewBufferManager(
|
||||
gui.Log,
|
||||
view,
|
||||
func() {
|
||||
view.Clear()
|
||||
},
|
||||
func() {
|
||||
gui.g.Update(func(*gocui.Gui) error { return nil })
|
||||
})
|
||||
gui.viewBufferManagerMap[view.Name()] = manager
|
||||
}
|
||||
|
||||
return manager
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spkg/bom"
|
||||
@@ -101,9 +102,21 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
case "status":
|
||||
return gui.handleStatusSelect(g, v)
|
||||
case "files":
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
return gui.focusAndSelectFile(g, v)
|
||||
case "branches":
|
||||
return gui.handleBranchSelect(g, v)
|
||||
branchesView := gui.getBranchesView()
|
||||
switch branchesView.Context {
|
||||
case "local-branches":
|
||||
return gui.handleBranchSelect(g, v)
|
||||
case "remotes":
|
||||
return gui.handleRemoteSelect(g, v)
|
||||
case "remote-branches":
|
||||
return gui.handleRemoteBranchSelect(g, v)
|
||||
case "tags":
|
||||
return gui.handleTagSelect(g, v)
|
||||
default:
|
||||
return errors.New("unknown branches panel context: " + branchesView.Context)
|
||||
}
|
||||
case "commits":
|
||||
return gui.handleCommitSelect(g, v)
|
||||
case "commitFiles":
|
||||
@@ -117,7 +130,7 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
case "credentials":
|
||||
return gui.handleCredentialsViewFocused(g, v)
|
||||
case "main":
|
||||
if gui.State.Context == "merging" {
|
||||
if gui.State.MainContext == "merging" {
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
v.Highlight = false
|
||||
@@ -315,6 +328,11 @@ func (gui *Gui) getCommitFilesView() *gocui.View {
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getMenuView() *gocui.View {
|
||||
v, _ := gui.g.View("menu")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||
return strings.TrimSpace(v.Buffer())
|
||||
}
|
||||
@@ -362,19 +380,17 @@ func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
|
||||
if up {
|
||||
if *line == -1 || *line == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
*line--
|
||||
func (gui *Gui) changeSelectedLine(line *int, total int, change int) {
|
||||
// TODO: find out why we're doing this
|
||||
if *line == -1 {
|
||||
return
|
||||
}
|
||||
if *line+change < 0 {
|
||||
*line = 0
|
||||
} else if *line+change >= total {
|
||||
*line = total - 1
|
||||
} else {
|
||||
if *line == -1 || *line == total-1 {
|
||||
return
|
||||
}
|
||||
|
||||
*line++
|
||||
*line += change
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +422,7 @@ func (gui *Gui) renderPanelOptions() error {
|
||||
case "menu":
|
||||
return gui.renderMenuOptions()
|
||||
case "main":
|
||||
if gui.State.Context == "merging" {
|
||||
if gui.State.MainContext == "merging" {
|
||||
return gui.renderMergeOptions()
|
||||
}
|
||||
}
|
||||
@@ -449,3 +465,12 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
|
||||
|
||||
return handleSelect(gui.g, v)
|
||||
}
|
||||
|
||||
// often gocui wants functions in the form `func(g *gocui.Gui, v *gocui.View) error`
|
||||
// but sometimes we just have a function that returns an error, so this is a
|
||||
// convenience wrapper to give gocui what it wants.
|
||||
func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
return f()
|
||||
}
|
||||
}
|
||||
|
||||
100
pkg/gui/workspace_reset_options_panel.go
Normal file
100
pkg/gui/workspace_reset_options_panel.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type workspaceResetOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *workspaceResetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*workspaceResetOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChangesToAllFiles"),
|
||||
command: "reset --hard HEAD && git clean -fd",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetAndClean(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAnyUnstagedChanges"),
|
||||
command: "git checkout -- .",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.DiscardAnyUnstagedFileChanges(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardUntrackedFiles"),
|
||||
command: "git clean -fd",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.RemoveUntrackedFiles(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("softReset"),
|
||||
command: "git reset --soft HEAD",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardReset"),
|
||||
command: "git reset --hard HEAD",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardResetUpstream"),
|
||||
command: "git reset --hard @{upstream}",
|
||||
handler: func() error {
|
||||
if err := gui.GitCommand.ResetHard("@{upstream}"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
}
|
||||
@@ -337,9 +337,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "nieuw gefocussed weergave is {{.newFocusedView}}",
|
||||
}, &i18n.Message{
|
||||
ID: "CantCloseConfirmationPrompt",
|
||||
Other: "Kon de bevestiging prompt niet sluiten: {{.error}}",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeAborted",
|
||||
Other: "Merge afgebroken",
|
||||
@@ -760,6 +757,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstream",
|
||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -147,6 +147,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "resolveMergeConflicts",
|
||||
Other: "resolve merge conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeConflictsTitle",
|
||||
Other: "Merge Conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "checkout",
|
||||
Other: "checkout",
|
||||
@@ -218,7 +221,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "{{.selectedBranchName}} is not fully merged. Are you sure you want to delete it?",
|
||||
}, &i18n.Message{
|
||||
ID: "rebaseBranch",
|
||||
Other: "rebase branch",
|
||||
Other: "rebase checked-out branch onto this branch",
|
||||
}, &i18n.Message{
|
||||
ID: "CantRebaseOntoSelf",
|
||||
Other: "You cannot rebase a branch onto itself",
|
||||
@@ -396,9 +399,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "new focused view is {{.newFocusedView}}",
|
||||
}, &i18n.Message{
|
||||
ID: "CantCloseConfirmationPrompt",
|
||||
Other: "Could not close confirmation prompt: {{.error}}",
|
||||
}, &i18n.Message{
|
||||
ID: "NoChangedFiles",
|
||||
Other: "No changed files",
|
||||
@@ -753,6 +753,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "hardReset",
|
||||
Other: "hard reset",
|
||||
}, &i18n.Message{
|
||||
ID: "hardResetUpstream",
|
||||
Other: "hard reset to upstream branch",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetOptions",
|
||||
Other: `view reset options`,
|
||||
@@ -822,6 +825,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "toggleAddToPatch",
|
||||
Other: "toggle file included in patch",
|
||||
}, &i18n.Message{
|
||||
ID: "ViewPatchOptions",
|
||||
Other: "view custom patch options",
|
||||
}, &i18n.Message{
|
||||
ID: "PatchOptionsTitle",
|
||||
Other: "Patch Options",
|
||||
@@ -843,6 +849,96 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "notTrackingRemote",
|
||||
Other: "(not tracking any remote)",
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
}, &i18n.Message{
|
||||
ID: "addNewRemote",
|
||||
Other: `add new remote`,
|
||||
}, &i18n.Message{
|
||||
ID: "newRemoteName",
|
||||
Other: `New remote name:`,
|
||||
}, &i18n.Message{
|
||||
ID: "newRemoteUrl",
|
||||
Other: `New remote url:`,
|
||||
}, &i18n.Message{
|
||||
ID: "editRemoteName",
|
||||
Other: `Enter updated remote name for {{ .remoteName }}:`,
|
||||
}, &i18n.Message{
|
||||
ID: "editRemoteUrl",
|
||||
Other: `Enter updated remote url for {{ .remoteName }}:`,
|
||||
}, &i18n.Message{
|
||||
ID: "removeRemote",
|
||||
Other: `remove remote`,
|
||||
}, &i18n.Message{
|
||||
ID: "removeRemotePrompt",
|
||||
Other: "Are you sure you want to remove remote",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteRemoteBranch",
|
||||
Other: "Delete Remote Branch",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteRemoteBranchMessage",
|
||||
Other: "Are you sure you want to delete remote branch",
|
||||
}, &i18n.Message{
|
||||
ID: "setUpstream",
|
||||
Other: "set as upstream of checked-out branch",
|
||||
}, &i18n.Message{
|
||||
ID: "SetUpstreamTitle",
|
||||
Other: "Set upstream branch",
|
||||
}, &i18n.Message{
|
||||
ID: "SetUpstreamMessage",
|
||||
Other: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
|
||||
}, &i18n.Message{
|
||||
ID: "editRemote",
|
||||
Other: "edit remote",
|
||||
}, &i18n.Message{
|
||||
ID: "tagCommit",
|
||||
Other: "tag commit",
|
||||
}, &i18n.Message{
|
||||
ID: "TagNameTitle",
|
||||
Other: "Tag name:",
|
||||
}, &i18n.Message{
|
||||
ID: "deleteTag",
|
||||
Other: "delete tag",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteTagTitle",
|
||||
Other: "Delete tag",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteTagPrompt",
|
||||
Other: "Are you sure you want to delete tag '{{.tagName}}'?",
|
||||
}, &i18n.Message{
|
||||
ID: "PushTagTitle",
|
||||
Other: "remote to push tag '{{.tagName}}' to:",
|
||||
}, &i18n.Message{
|
||||
ID: "pushTag",
|
||||
Other: "push tag",
|
||||
}, &i18n.Message{
|
||||
ID: "createTag",
|
||||
Other: "create tag",
|
||||
}, &i18n.Message{
|
||||
ID: "CreateTagTitle",
|
||||
Other: "Tag name:",
|
||||
}, &i18n.Message{
|
||||
ID: "fetchRemote",
|
||||
Other: "fetch remote",
|
||||
}, &i18n.Message{
|
||||
ID: "FetchingRemoteStatus",
|
||||
Other: "fetching remote",
|
||||
}, &i18n.Message{
|
||||
ID: "checkoutCommit",
|
||||
Other: "checkout commit",
|
||||
}, &i18n.Message{
|
||||
ID: "SureCheckoutThisCommit",
|
||||
Other: "Are you sure you want to checkout this commit?",
|
||||
}, &i18n.Message{
|
||||
ID: "gitFlowOptions",
|
||||
Other: "show git-flow options",
|
||||
}, &i18n.Message{
|
||||
ID: "NotAGitFlowBranch",
|
||||
Other: "This does not seem to be a git flow branch",
|
||||
}, &i18n.Message{
|
||||
ID: "NewBranchNamePrompt",
|
||||
Other: "new {{.branchType}} name:",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -329,9 +329,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "nowy skupiony widok to {{.newFocusedView}}",
|
||||
}, &i18n.Message{
|
||||
ID: "CantCloseConfirmationPrompt",
|
||||
Other: "Nie można zamknąć monitu potwierdzenia: {{.error}}",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeAborted",
|
||||
Other: "Scalanie anulowane",
|
||||
@@ -743,6 +740,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstream",
|
||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
228
pkg/tasks/tasks.go
Normal file
228
pkg/tasks/tasks.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
stop chan struct{}
|
||||
stopped bool
|
||||
stopMutex sync.Mutex
|
||||
notifyStopped chan struct{}
|
||||
Log *logrus.Entry
|
||||
f func(chan struct{}) error
|
||||
}
|
||||
|
||||
type ViewBufferManager struct {
|
||||
writer io.Writer
|
||||
waitingTask *Task
|
||||
currentTask *Task
|
||||
waitingMutex sync.Mutex
|
||||
taskIDMutex sync.Mutex
|
||||
Log *logrus.Entry
|
||||
newTaskId int
|
||||
readLines chan int
|
||||
|
||||
// beforeStart is the function that is called before starting a new task
|
||||
beforeStart func()
|
||||
refreshView func()
|
||||
}
|
||||
|
||||
func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func()) *ViewBufferManager {
|
||||
return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, readLines: make(chan int, 1024)}
|
||||
}
|
||||
|
||||
func (m *ViewBufferManager) ReadLines(n int) {
|
||||
go func() {
|
||||
m.readLines <- n
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan struct{}) error {
|
||||
return func(stop chan struct{}) error {
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
if cmd.ProcessState.ExitCode() == -1 {
|
||||
if err := kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// not sure if it's the right move to redefine this or not
|
||||
m.readLines = make(chan int, 1024)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
loaded := false
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 100)
|
||||
defer ticker.Stop()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
m.writer.Write([]byte("loading..."))
|
||||
m.refreshView()
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case linesToRead := <-m.readLines:
|
||||
for i := 0; i < linesToRead; i++ {
|
||||
ok := scanner.Scan()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
loaded = true
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
if !ok {
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
}
|
||||
m.refreshView()
|
||||
case <-stop:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
|
||||
close(done)
|
||||
}()
|
||||
|
||||
m.readLines <- linesToRead
|
||||
|
||||
<-done
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the task manager, killing whatever task may currently be running
|
||||
func (t *ViewBufferManager) Close() {
|
||||
if t.currentTask == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
t.currentTask.Stop()
|
||||
c <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
return
|
||||
case <-time.After(3 * time.Second):
|
||||
fmt.Println("cannot kill child process")
|
||||
}
|
||||
}
|
||||
|
||||
// different kinds of tasks:
|
||||
// 1) command based, where the manager can be asked to read more lines, but the command can be killed
|
||||
// 2) string based, where the manager can also be asked to read more lines
|
||||
|
||||
func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
|
||||
go func() {
|
||||
m.taskIDMutex.Lock()
|
||||
m.newTaskId++
|
||||
taskID := m.newTaskId
|
||||
m.Log.Infof("starting task %d", taskID)
|
||||
m.taskIDMutex.Unlock()
|
||||
|
||||
m.waitingMutex.Lock()
|
||||
defer m.waitingMutex.Unlock()
|
||||
if taskID < m.newTaskId {
|
||||
return
|
||||
}
|
||||
|
||||
stop := make(chan struct{})
|
||||
notifyStopped := make(chan struct{})
|
||||
|
||||
if m.currentTask != nil {
|
||||
m.Log.Info("asking task to stop")
|
||||
m.currentTask.Stop()
|
||||
m.Log.Info("task stopped")
|
||||
}
|
||||
|
||||
m.currentTask = &Task{
|
||||
stop: stop,
|
||||
notifyStopped: notifyStopped,
|
||||
Log: m.Log,
|
||||
f: f,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := f(stop); err != nil {
|
||||
m.Log.Error(err) // might need an onError callback
|
||||
}
|
||||
|
||||
m.Log.Infof("returning from task %d", taskID)
|
||||
close(notifyStopped)
|
||||
}()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) Stop() {
|
||||
t.stopMutex.Lock()
|
||||
defer t.stopMutex.Unlock()
|
||||
if t.stopped {
|
||||
return
|
||||
}
|
||||
close(t.stop)
|
||||
t.Log.Info("closed stop channel, waiting for notifyStopped message")
|
||||
<-t.notifyStopped
|
||||
t.Log.Info("received notifystopped message")
|
||||
t.stopped = true
|
||||
return
|
||||
}
|
||||
|
||||
// kill kills a process
|
||||
func kill(cmd *exec.Cmd) error {
|
||||
if cmd.Process == nil {
|
||||
// somebody got to it before we were able to, poor bastard
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package test
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -43,3 +44,15 @@ func CreateMockCommand(t *testing.T, swappers []*CommandSwapper) func(cmd string
|
||||
return command
|
||||
}
|
||||
}
|
||||
|
||||
func AssertContainsMatch(t *testing.T, strs []string, pattern *regexp.Regexp, message string) {
|
||||
t.Helper()
|
||||
|
||||
for _, str := range strs {
|
||||
if pattern.Match([]byte(str)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
assert.Fail(t, message)
|
||||
}
|
||||
|
||||
@@ -299,3 +299,14 @@ func DifferenceInt(a, b []int) []int {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// used to keep a number n between 0 and max, allowing for wraparounds
|
||||
func ModuloWithWrap(n, max int) int {
|
||||
if n >= max {
|
||||
return n % max
|
||||
} else if n < 0 {
|
||||
return max + n
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/integrii/flaggy/.gitignore
generated
vendored
2
vendor/github.com/integrii/flaggy/.gitignore
generated
vendored
@@ -12,3 +12,5 @@
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
.idea/
|
||||
|
||||
34
vendor/github.com/integrii/flaggy/README.md
generated
vendored
34
vendor/github.com/integrii/flaggy/README.md
generated
vendored
@@ -172,41 +172,57 @@ Other more specific types can also be used as flag types. They will be automati
|
||||
- time.Duration
|
||||
- []time.Duration
|
||||
|
||||
# Recommended Program Structure
|
||||
# An Example Program
|
||||
|
||||
Best practice when using flaggy includes setting your program's name, description, and version (at build time).
|
||||
Best practice when using flaggy includes setting your program's name, description, and version (at build time) as shown in this example program.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/integrii/flaggy"
|
||||
|
||||
// make a variable for the version which will be set at build time
|
||||
// Make a variable for the version which will be set at build time.
|
||||
var version = "unknown"
|
||||
|
||||
// keep subcommands as globals so you can easily check if they were used later on
|
||||
// Keep subcommands as globals so you can easily check if they were used later on.
|
||||
var mySubcommand *flaggy.Subcommand
|
||||
|
||||
// Setup the variables you want your incoming flags to set.
|
||||
var testVar string
|
||||
|
||||
// If you would like an environment variable as the default for a value, just populate the flag
|
||||
// with the value of the environment by default. If the flag corresponding to this value is not
|
||||
// used, then it will not be changed.
|
||||
var myVar = os.Getenv("MY_VAR")
|
||||
|
||||
|
||||
func init() {
|
||||
// Set your program's name and description. These appear in help output.
|
||||
flaggy.SetName("Test Program")
|
||||
flaggy.SetDescription("A little example program")
|
||||
|
||||
// you can disable various things by changing bools on the default parser
|
||||
// (or your own parser if you have created one)
|
||||
// You can disable various things by changing bools on the default parser
|
||||
// (or your own parser if you have created one).
|
||||
flaggy.DefaultParser.ShowHelpOnUnexpected = false
|
||||
|
||||
// you can set a help prepend or append on the default parser
|
||||
// You can set a help prepend or append on the default parser.
|
||||
flaggy.DefaultParser.AdditionalHelpPrepend = "http://github.com/integrii/flaggy"
|
||||
|
||||
// Add a flag to the main program (this will be available in all subcommands as well).
|
||||
flaggy.String(&testVar, "tv", "testVariable", "A variable just for testing things!")
|
||||
|
||||
// create any subcommands and set their parameters
|
||||
// Create any subcommands and set their parameters.
|
||||
mySubcommand = flaggy.NewSubcommand("mySubcommand")
|
||||
mySubcommand.Description = "My great subcommand!"
|
||||
|
||||
// Add a flag to the subcommand.
|
||||
mySubcommand.String(&myVar, "mv", "myVariable", "A variable just for me!")
|
||||
|
||||
// set the version and parse all inputs into variables
|
||||
// Set the version and parse all inputs into variables.
|
||||
flaggy.SetVersion(version)
|
||||
flaggy.Parse()
|
||||
}
|
||||
|
||||
func main(){
|
||||
if mySubcommand.Used {
|
||||
...
|
||||
|
||||
8
vendor/github.com/integrii/flaggy/argumentParser.go
generated
vendored
8
vendor/github.com/integrii/flaggy/argumentParser.go
generated
vendored
@@ -5,22 +5,20 @@ package flaggy
|
||||
// The return values represent the key being set, and any errors
|
||||
// returned when setting the key, such as failures to convert the string
|
||||
// into the appropriate flag value. We stop assigning values as soon
|
||||
// as we find a parser that accepts it.
|
||||
// as we find a any parser that accepts it.
|
||||
func setValueForParsers(key string, value string, parsers ...ArgumentParser) (bool, error) {
|
||||
|
||||
var valueWasSet bool
|
||||
|
||||
for _, p := range parsers {
|
||||
valueWasSet, err := p.SetValueForKey(key, value)
|
||||
if err != nil {
|
||||
return valueWasSet, err
|
||||
}
|
||||
if valueWasSet {
|
||||
break
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return valueWasSet, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ArgumentParser represents a parser or subcommand
|
||||
|
||||
12
vendor/github.com/integrii/flaggy/main.go
generated
vendored
12
vendor/github.com/integrii/flaggy/main.go
generated
vendored
@@ -327,6 +327,18 @@ func exitOrPanic(code int) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// ShowHelpOnUnexpectedEnable enables the ShowHelpOnUnexpected behavior on the
|
||||
// default parser. This causes unknown inputs to error out.
|
||||
func ShowHelpOnUnexpectedEnable() {
|
||||
DefaultParser.ShowHelpOnUnexpected = true
|
||||
}
|
||||
|
||||
// ShowHelpOnUnexpectedDisable disables the ShowHelpOnUnexpected behavior on the
|
||||
// default parser. This causes unknown inputs to error out.
|
||||
func ShowHelpOnUnexpectedDisable() {
|
||||
DefaultParser.ShowHelpOnUnexpected = false
|
||||
}
|
||||
|
||||
// AddPositionalValue adds a positional value to the main parser at the global
|
||||
// context
|
||||
func AddPositionalValue(assignmentVar *string, name string, relativePosition int, required bool, description string) {
|
||||
|
||||
23
vendor/github.com/integrii/flaggy/parsedValue.go
generated
vendored
Normal file
23
vendor/github.com/integrii/flaggy/parsedValue.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package flaggy
|
||||
|
||||
// parsedValue represents a flag or subcommand that was parsed. Primairily used
|
||||
// to account for all parsed values in order to determine if unknown values were
|
||||
// passed to the root parser after all subcommands have been parsed.
|
||||
type parsedValue struct {
|
||||
Key string
|
||||
Value string
|
||||
IsPositional bool // indicates that this value was positional and not a key/value
|
||||
}
|
||||
|
||||
// newParsedValue creates and returns a new parsedValue struct with the
|
||||
// supplied values set
|
||||
func newParsedValue(key string, value string, isPositional bool) parsedValue {
|
||||
if len(key) == 0 && len(value) == 0 {
|
||||
panic("cant add parsed value with no key or value")
|
||||
}
|
||||
return parsedValue{
|
||||
Key: key,
|
||||
Value: value,
|
||||
IsPositional: isPositional,
|
||||
}
|
||||
}
|
||||
96
vendor/github.com/integrii/flaggy/parser.go
generated
vendored
96
vendor/github.com/integrii/flaggy/parser.go
generated
vendored
@@ -4,18 +4,20 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Parser represents the set of vars and subcommands we are expecting
|
||||
// from our input args, and the parser than handles them all.
|
||||
// Parser represents the set of flags and subcommands we are expecting
|
||||
// from our input arguments. Parser is the top level struct responsible for
|
||||
// parsing an entire set of subcommands and flags.
|
||||
type Parser struct {
|
||||
Subcommand
|
||||
Version string // the optional version of the parser.
|
||||
ShowHelpWithHFlag bool // display help when -h or --help passed
|
||||
ShowVersionWithVersionFlag bool // display the version when --version passed
|
||||
ShowHelpOnUnexpected bool // display help when an unexpected flag is passed
|
||||
ShowHelpOnUnexpected bool // display help when an unexpected flag or subcommand is passed
|
||||
TrailingArguments []string // everything after a -- is placed here
|
||||
HelpTemplate *template.Template // template for Help output
|
||||
trailingArgumentsExtracted bool // indicates that trailing args have been parsed and should not be appended again
|
||||
@@ -46,8 +48,89 @@ func (p *Parser) ParseArgs(args []string) error {
|
||||
return errors.New("Parser.Parse() called twice on parser with name: " + " " + p.Name + " " + p.ShortName)
|
||||
}
|
||||
p.parsed = true
|
||||
// debugPrint("Kicking off parsing with args:", args)
|
||||
return p.parse(p, args, 0)
|
||||
|
||||
debugPrint("Kicking off parsing with args:", args)
|
||||
err := p.parse(p, args, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we are set to crash on unexpected args, look for those here TODO
|
||||
if p.ShowHelpOnUnexpected {
|
||||
parsedValues := p.findAllParsedValues()
|
||||
debugPrint("parsedValues:", parsedValues)
|
||||
argsNotParsed := findArgsNotInParsedValues(args, parsedValues)
|
||||
if len(argsNotParsed) > 0 {
|
||||
// flatten out unused args for our error message
|
||||
var argsNotParsedFlat string
|
||||
for _, a := range argsNotParsed {
|
||||
argsNotParsedFlat = argsNotParsedFlat + " " + a
|
||||
}
|
||||
p.ShowHelpAndExit("Unknown arguments supplied: " + argsNotParsedFlat)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findArgsNotInParsedValues finds arguments not used in parsed values. The
|
||||
// incoming args should be in the order supplied by the user and should not
|
||||
// include the invoked binary, which is normally the first thing in os.Args.
|
||||
func findArgsNotInParsedValues(args []string, parsedValues []parsedValue) []string {
|
||||
var argsNotUsed []string
|
||||
var skipNext bool
|
||||
for _, a := range args {
|
||||
|
||||
// if the final argument (--) is seen, then we stop checking because all
|
||||
// further values are trailing arguments.
|
||||
if determineArgType(a) == argIsFinal {
|
||||
return argsNotUsed
|
||||
}
|
||||
|
||||
// allow for skipping the next arg when needed
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
// strip flag slashes from incoming arguments so they match up with the
|
||||
// keys from parsedValues.
|
||||
arg := parseFlagToName(a)
|
||||
|
||||
// indicates that we found this arg used in one of the parsed values. Used
|
||||
// to indicate which values should be added to argsNotUsed.
|
||||
var foundArgUsed bool
|
||||
|
||||
// search all args for a corresponding parsed value
|
||||
for _, pv := range parsedValues {
|
||||
// this argumenet was a key
|
||||
// debugPrint(pv.Key, "==", arg)
|
||||
debugPrint(pv.Key + "==" + arg + " || (" + strconv.FormatBool(pv.IsPositional) + " && " + pv.Value + " == " + arg + ")")
|
||||
if pv.Key == arg || (pv.IsPositional && pv.Value == arg) {
|
||||
debugPrint("Found matching parsed arg for " + pv.Key)
|
||||
foundArgUsed = true // the arg was used in this parsedValues set
|
||||
// if the value is not a positional value and the parsed value had a
|
||||
// value that was not blank, we skip the next value in the argument list
|
||||
if !pv.IsPositional && len(pv.Value) > 0 {
|
||||
skipNext = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// this prevents excessive parsed values from being checked after we find
|
||||
// the arg used for the first time
|
||||
if foundArgUsed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if the arg was not used in any parsed values, then we add it to the slice
|
||||
// of arguments not used
|
||||
if !foundArgUsed {
|
||||
argsNotUsed = append(argsNotUsed, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return argsNotUsed
|
||||
}
|
||||
|
||||
// ShowVersionAndExit shows the version of this parser
|
||||
@@ -105,7 +188,8 @@ func (p *Parser) ShowHelpWithMessage(message string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Disable show version with --version. It is enabled by default.
|
||||
// DisableShowVersionWithVersion disables the showing of version information
|
||||
// with --version. It is enabled by default.
|
||||
func (p *Parser) DisableShowVersionWithVersion() {
|
||||
p.ShowVersionWithVersionFlag = false
|
||||
}
|
||||
|
||||
109
vendor/github.com/integrii/flaggy/subCommand.go
generated
vendored
109
vendor/github.com/integrii/flaggy/subCommand.go
generated
vendored
@@ -23,15 +23,20 @@ type Subcommand struct {
|
||||
Subcommands []*Subcommand
|
||||
Flags []*Flag
|
||||
PositionalFlags []*PositionalValue
|
||||
AdditionalHelpPrepend string // additional prepended message when Help is displayed
|
||||
AdditionalHelpAppend string // additional appended message when Help is displayed
|
||||
Used bool // indicates this subcommand was found and parsed
|
||||
Hidden bool // indicates this subcommand should be hidden from help
|
||||
ParsedValues []parsedValue // a list of values and positionals parsed
|
||||
AdditionalHelpPrepend string // additional prepended message when Help is displayed
|
||||
AdditionalHelpAppend string // additional appended message when Help is displayed
|
||||
Used bool // indicates this subcommand was found and parsed
|
||||
Hidden bool // indicates this subcommand should be hidden from help
|
||||
}
|
||||
|
||||
// NewSubcommand creates a new subcommand that can have flags or PositionalFlags
|
||||
// added to it. The position starts with 1, not 0
|
||||
func NewSubcommand(name string) *Subcommand {
|
||||
if len(name) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Error creating subcommand (NewSubcommand()). No subcommand name was specified.")
|
||||
exitOrPanic(2)
|
||||
}
|
||||
newSC := &Subcommand{
|
||||
Name: name,
|
||||
}
|
||||
@@ -39,10 +44,11 @@ func NewSubcommand(name string) *Subcommand {
|
||||
}
|
||||
|
||||
// parseAllFlagsFromArgs parses the non-positional flags such as -f or -v=value
|
||||
// out of the supplied args and returns the positional items in order.
|
||||
// out of the supplied args and returns the resulting positional items in order,
|
||||
// all the flag names found (without values), a bool to indicate if help was
|
||||
// requested, and any errors found during parsing
|
||||
func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string, bool, error) {
|
||||
|
||||
var err error
|
||||
var positionalOnlyArguments []string
|
||||
var helpRequested bool // indicates the user has supplied -h and we
|
||||
// should render help if we are the last subcommand
|
||||
@@ -58,7 +64,7 @@ func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string,
|
||||
// find all the normal flags (not positional) and parse them out
|
||||
for i, a := range args {
|
||||
|
||||
debugPrint("parsing arg", 1, a)
|
||||
debugPrint("parsing arg:", a)
|
||||
|
||||
// evaluate if there is a following arg to avoid panics
|
||||
var nextArgExists bool
|
||||
@@ -121,62 +127,107 @@ func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string,
|
||||
// this positional argument into a slice of their own, so that
|
||||
// we can determine if its a subcommand or positional value later
|
||||
positionalOnlyArguments = append(positionalOnlyArguments, a)
|
||||
case argIsFlagWithSpace:
|
||||
// track this as a parsed value with the subcommand
|
||||
sc.addParsedPositionalValue(a)
|
||||
case argIsFlagWithSpace: // a flag with a space. ex) -k v or --key value
|
||||
a = parseFlagToName(a)
|
||||
|
||||
// debugPrint("Arg", i, "is flag with space:", a)
|
||||
// parse next arg as value to this flag and apply to subcommand flags
|
||||
// if the flag is a bool flag, then we check for a following positional
|
||||
// and skip it if necessary
|
||||
if flagIsBool(sc, p, a) {
|
||||
debugPrint(sc.Name, "bool flag", a, "next var is:", nextArg)
|
||||
_, err = setValueForParsers(a, "true", p, sc)
|
||||
// set the value in this subcommand and its root parser
|
||||
valueSet, err := setValueForParsers(a, "true", p, sc)
|
||||
|
||||
// if an error occurs, just return it and quit parsing
|
||||
if err != nil {
|
||||
return []string{}, false, err
|
||||
}
|
||||
// by default, we just assign the next argument to the value and continue
|
||||
|
||||
// log all values parsed by this subcommand. We leave the value blank
|
||||
// because the bool value had no explicit true or false supplied
|
||||
if valueSet {
|
||||
sc.addParsedFlag(a, "")
|
||||
}
|
||||
|
||||
// we've found and set a standalone bool flag, so we move on to the next
|
||||
// argument in the list of arguments
|
||||
continue
|
||||
}
|
||||
|
||||
skipNext = true
|
||||
debugPrint(sc.Name, "NOT bool flag", a)
|
||||
// debugPrint(sc.Name, "NOT bool flag", a)
|
||||
|
||||
// if the next arg was not found, then show a Help message
|
||||
if !nextArgExists {
|
||||
p.ShowHelpWithMessage("Expected a following arg for flag " + a + ", but it did not exist.")
|
||||
exitOrPanic(2)
|
||||
}
|
||||
_, err = setValueForParsers(a, nextArg, p, sc)
|
||||
valueSet, err := setValueForParsers(a, nextArg, p, sc)
|
||||
if err != nil {
|
||||
return []string{}, false, err
|
||||
}
|
||||
case argIsFlagWithValue:
|
||||
|
||||
// log all parsed values in the subcommand
|
||||
if valueSet {
|
||||
sc.addParsedFlag(a, nextArg)
|
||||
}
|
||||
case argIsFlagWithValue: // a flag with an equals sign. ex) -k=v or --key=value
|
||||
// debugPrint("Arg", i, "is flag with value:", a)
|
||||
a = parseFlagToName(a)
|
||||
|
||||
// parse flag into key and value and apply to subcommand flags
|
||||
key, val := parseArgWithValue(a)
|
||||
_, err = setValueForParsers(key, val, p, sc)
|
||||
|
||||
// set the value in this subcommand and its root parser
|
||||
valueSet, err := setValueForParsers(key, val, p, sc)
|
||||
if err != nil {
|
||||
return []string{}, false, err
|
||||
}
|
||||
// if this flag type was found and not set, and the parser is set to show
|
||||
// Help when an unknown flag is found, then show Help and exit.
|
||||
}
|
||||
|
||||
// log all values parsed by the subcommand
|
||||
if valueSet {
|
||||
sc.addParsedFlag(a, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return positionalOnlyArguments, helpRequested, nil
|
||||
}
|
||||
|
||||
// Parse causes the argument parser to parse based on the supplied []string.
|
||||
// depth specifies the non-flag subcommand positional depth
|
||||
// findAllParsedValues finds all values parsed by all subcommands and this
|
||||
// subcommand and its child subcommands
|
||||
func (sc *Subcommand) findAllParsedValues() []parsedValue {
|
||||
parsedValues := sc.ParsedValues
|
||||
for _, sc := range sc.Subcommands {
|
||||
// skip unused subcommands
|
||||
if !sc.Used {
|
||||
continue
|
||||
}
|
||||
parsedValues = append(parsedValues, sc.findAllParsedValues()...)
|
||||
}
|
||||
return parsedValues
|
||||
}
|
||||
|
||||
// parse causes the argument parser to parse based on the supplied []string.
|
||||
// depth specifies the non-flag subcommand positional depth. A slice of flags
|
||||
// and subcommands parsed is returned so that the parser can ultimately decide
|
||||
// if there were any unexpected values supplied by the user
|
||||
func (sc *Subcommand) parse(p *Parser, args []string, depth int) error {
|
||||
|
||||
debugPrint("- Parsing subcommand", sc.Name, "with depth of", depth, "and args", args)
|
||||
|
||||
// if a command is parsed, its used
|
||||
sc.Used = true
|
||||
debugPrint("used subcommand", sc.Name, sc.ShortName)
|
||||
if len(sc.Name) > 0 {
|
||||
sc.addParsedPositionalValue(sc.Name)
|
||||
}
|
||||
if len(sc.ShortName) > 0 {
|
||||
sc.addParsedPositionalValue(sc.ShortName)
|
||||
}
|
||||
|
||||
// as subcommands are used, they become the context of the parser. This helps
|
||||
// us understand how to display help based on which subcommand is being used
|
||||
@@ -191,9 +242,10 @@ func (sc *Subcommand) parse(p *Parser, args []string, depth int) error {
|
||||
sc.ensureNoConflictWithBuiltinVersion()
|
||||
}
|
||||
|
||||
// Parse the normal flags out of the argument list and retain the positionals.
|
||||
// Apply the flags to the parent parser and the current subcommand context.
|
||||
// ./command -f -z subcommand someVar -b becomes ./command subcommand somevar
|
||||
// Parse the normal flags out of the argument list and return the positionals
|
||||
// (subcommands and positional values), along with the flags used.
|
||||
// Then the flag values are applied to the parent parser and the current
|
||||
// subcommand being parsed.
|
||||
positionalOnlyArguments, helpRequested, err := sc.parseAllFlagsFromArgs(p, args)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -292,7 +344,7 @@ func (sc *Subcommand) parse(p *Parser, args []string, depth int) error {
|
||||
}
|
||||
|
||||
// find any positionals that were not used on subcommands that were
|
||||
// found and throw help (unknown argument)
|
||||
// found and throw help (unknown argument) in the global parse or subcommand
|
||||
for _, pv := range p.PositionalFlags {
|
||||
if pv.Required && !pv.Found {
|
||||
p.ShowHelpWithMessage("Required global positional variable " + pv.Name + " not found at position " + strconv.Itoa(pv.Position))
|
||||
@@ -309,6 +361,17 @@ func (sc *Subcommand) parse(p *Parser, args []string, depth int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addParsedFlag makes it easy to append flag values parsed by the subcommand
|
||||
func (sc *Subcommand) addParsedFlag(key string, value string) {
|
||||
sc.ParsedValues = append(sc.ParsedValues, newParsedValue(key, value, false))
|
||||
}
|
||||
|
||||
// addParsedPositionalValue makes it easy to append positionals parsed by the
|
||||
// subcommand
|
||||
func (sc *Subcommand) addParsedPositionalValue(value string) {
|
||||
sc.ParsedValues = append(sc.ParsedValues, newParsedValue("", value, true))
|
||||
}
|
||||
|
||||
// FlagExists lets you know if the flag name exists as either a short or long
|
||||
// name in the (sub)command
|
||||
func (sc *Subcommand) FlagExists(name string) bool {
|
||||
|
||||
17
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
17
vendor/github.com/jesseduffield/gocui/escape.go
generated
vendored
@@ -5,8 +5,10 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"github.com/go-errors/errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
)
|
||||
|
||||
type escapeInterpreter struct {
|
||||
@@ -15,6 +17,7 @@ type escapeInterpreter struct {
|
||||
csiParam []string
|
||||
curFgColor, curBgColor Attribute
|
||||
mode OutputMode
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type escapeState int
|
||||
@@ -34,6 +37,9 @@ var (
|
||||
|
||||
// runes in case of error will output the non-parsed runes as a string.
|
||||
func (ei *escapeInterpreter) runes() []rune {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
switch ei.state {
|
||||
case stateNone:
|
||||
return []rune{0x1b}
|
||||
@@ -66,6 +72,9 @@ func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
|
||||
|
||||
// reset sets the escapeInterpreter in initial state.
|
||||
func (ei *escapeInterpreter) reset() {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
ei.state = stateNone
|
||||
ei.curFgColor = ColorDefault
|
||||
ei.curBgColor = ColorDefault
|
||||
@@ -76,6 +85,9 @@ func (ei *escapeInterpreter) reset() {
|
||||
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
||||
// it's not an escape sequence.
|
||||
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
// Sanity checks
|
||||
if len(ei.csiParam) > 20 {
|
||||
return false, errCSITooLong
|
||||
@@ -179,6 +191,9 @@ func (ei *escapeInterpreter) outputNormal() error {
|
||||
// 0x11 - 0xe8: 216 different colors
|
||||
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||
func (ei *escapeInterpreter) output256() error {
|
||||
ei.mutex.Lock()
|
||||
defer ei.mutex.Unlock()
|
||||
|
||||
if len(ei.csiParam) < 3 {
|
||||
return ei.outputNormal()
|
||||
}
|
||||
|
||||
37
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
37
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@@ -7,6 +7,7 @@ package gocui
|
||||
import (
|
||||
standardErrors "errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -89,6 +90,10 @@ type Gui struct {
|
||||
// SupportOverlaps is true when we allow for view edges to overlap with other
|
||||
// view edges
|
||||
SupportOverlaps bool
|
||||
|
||||
// tickingMutex ensures we don't have two loops ticking. The point of 'ticking'
|
||||
// is to refresh the gui rapidly so that loader characters can be animated.
|
||||
tickingMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
@@ -125,9 +130,7 @@ func NewGui(mode OutputMode, supportOverlaps bool) (*Gui, error) {
|
||||
// Close finalizes the library. It should be called after a successful
|
||||
// initialization and when gocui is not needed anymore.
|
||||
func (g *Gui) Close() {
|
||||
go func() {
|
||||
g.stop <- struct{}{}
|
||||
}()
|
||||
close(g.stop)
|
||||
termbox.Close()
|
||||
}
|
||||
|
||||
@@ -295,14 +298,14 @@ func (g *Gui) CurrentView() *View {
|
||||
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
||||
// (empty string) then the keybinding will apply to all views. key must
|
||||
// be a rune or a Key.
|
||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||
func (g *Gui) SetKeybinding(viewname string, contexts []string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||
var kb *keybinding
|
||||
|
||||
k, ch, err := getKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb = newKeybinding(viewname, k, ch, mod, handler)
|
||||
kb = newKeybinding(viewname, contexts, k, ch, mod, handler)
|
||||
g.keybindings = append(g.keybindings, kb)
|
||||
return nil
|
||||
}
|
||||
@@ -409,7 +412,6 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
||||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
g.loaderTick()
|
||||
if err := g.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -835,14 +837,25 @@ func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (g *Gui) loaderTick() {
|
||||
func (g *Gui) StartTicking() {
|
||||
go func() {
|
||||
for range time.Tick(time.Millisecond * 50) {
|
||||
for _, view := range g.Views() {
|
||||
if view.HasLoader {
|
||||
g.userEvents <- userEvent{func(g *Gui) error { return nil }}
|
||||
break
|
||||
g.tickingMutex.Lock()
|
||||
defer g.tickingMutex.Unlock()
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
for _, view := range g.Views() {
|
||||
if view.HasLoader {
|
||||
g.userEvents <- userEvent{func(g *Gui) error { return nil }}
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
return
|
||||
case <-g.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
20
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
20
vendor/github.com/jesseduffield/gocui/keybinding.go
generated
vendored
@@ -9,6 +9,7 @@ import "github.com/jesseduffield/termbox-go"
|
||||
// Keybidings are used to link a given key-press event with a handler.
|
||||
type keybinding struct {
|
||||
viewName string
|
||||
contexts []string
|
||||
key Key
|
||||
ch rune
|
||||
mod Modifier
|
||||
@@ -16,9 +17,10 @@ type keybinding struct {
|
||||
}
|
||||
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||
func newKeybinding(viewname string, contexts []string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
viewName: viewname,
|
||||
contexts: contexts,
|
||||
key: key,
|
||||
ch: ch,
|
||||
mod: mod,
|
||||
@@ -32,7 +34,7 @@ func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||
}
|
||||
|
||||
// matchView returns if the keybinding matches the current view.
|
||||
// matchView returns if the keybinding matches the current view (and the view's context)
|
||||
func (kb *keybinding) matchView(v *View) bool {
|
||||
// if the user is typing in a field, ignore char keys
|
||||
if v == nil {
|
||||
@@ -41,7 +43,19 @@ func (kb *keybinding) matchView(v *View) bool {
|
||||
if v.Editable == true && kb.ch != 0 {
|
||||
return false
|
||||
}
|
||||
return kb.viewName == v.name
|
||||
if kb.viewName != v.name {
|
||||
return false
|
||||
}
|
||||
// if the keybinding doesn't specify contexts, it applies for all contexts
|
||||
if len(kb.contexts) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, context := range kb.contexts {
|
||||
if context == v.Context {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Key represents special keys or keys combinations.
|
||||
|
||||
6
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
6
vendor/github.com/jesseduffield/gocui/view.go
generated
vendored
@@ -103,6 +103,8 @@ type View struct {
|
||||
|
||||
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
|
||||
ParentView *View
|
||||
|
||||
Context string // this is for assigning keybindings to a view only in certain contexts
|
||||
}
|
||||
|
||||
type viewLine struct {
|
||||
@@ -456,6 +458,8 @@ func (v *View) clearRunes() {
|
||||
// BufferLines returns the lines in the view's internal
|
||||
// buffer.
|
||||
func (v *View) BufferLines() []string {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
lines := make([]string, len(v.lines))
|
||||
for i, l := range v.lines {
|
||||
str := lineType(l).String()
|
||||
@@ -474,6 +478,8 @@ func (v *View) Buffer() string {
|
||||
// ViewBufferLines returns the lines in the view's internal
|
||||
// buffer that is shown to the user.
|
||||
func (v *View) ViewBufferLines() []string {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
lines := make([]string, len(v.viewLines))
|
||||
for i, l := range v.viewLines {
|
||||
str := lineType(l.line).String()
|
||||
|
||||
10
vendor/github.com/jesseduffield/pty/types.go
generated
vendored
10
vendor/github.com/jesseduffield/pty/types.go
generated
vendored
@@ -1,10 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
import "C"
|
||||
|
||||
type (
|
||||
_C_int C.int
|
||||
_C_uint C.uint
|
||||
)
|
||||
17
vendor/github.com/jesseduffield/pty/types_dragonfly.go
generated
vendored
17
vendor/github.com/jesseduffield/pty/types_dragonfly.go
generated
vendored
@@ -1,17 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
/*
|
||||
#define _KERNEL
|
||||
#include <sys/conf.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/filio.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||
)
|
||||
|
||||
type fiodgnameArg C.struct_fiodname_args
|
||||
15
vendor/github.com/jesseduffield/pty/types_freebsd.go
generated
vendored
15
vendor/github.com/jesseduffield/pty/types_freebsd.go
generated
vendored
@@ -1,15 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
/*
|
||||
#include <sys/param.h>
|
||||
#include <sys/filio.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||
)
|
||||
|
||||
type fiodgnameArg C.struct_fiodgname_arg
|
||||
14
vendor/github.com/jesseduffield/pty/types_openbsd.go
generated
vendored
14
vendor/github.com/jesseduffield/pty/types_openbsd.go
generated
vendored
@@ -1,14 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
/*
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/tty.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type ptmget C.struct_ptmget
|
||||
|
||||
var ioctl_PTMGET = C.PTMGET
|
||||
1
vendor/github.com/jesseduffield/roll/.gitignore
generated
vendored
1
vendor/github.com/jesseduffield/roll/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
||||
rollbar.test
|
||||
24
vendor/github.com/jesseduffield/roll/CHANGELOG
generated
vendored
24
vendor/github.com/jesseduffield/roll/CHANGELOG
generated
vendored
@@ -1,24 +0,0 @@
|
||||
0.2.0 - May 22nd, 2016
|
||||
====================
|
||||
|
||||
* Do not use title to determine fingerprint.
|
||||
|
||||
0.1.1 - August 24th, 2016
|
||||
=========================
|
||||
|
||||
* Fix Go 1.6 support by removing call to runtime.CallersFrames, which was added
|
||||
in Go 1.7.
|
||||
|
||||
0.1.0 - August 23rd, 2016
|
||||
=========================
|
||||
|
||||
* Allow passing in arbitrary function pointer stacks. (thanks @apg!)
|
||||
* Remove unneeded exported constants.
|
||||
* Make HTTP(S) endpoint configurable.
|
||||
* Remove unneeded debug print statement.
|
||||
|
||||
0.0.1 - January 19th, 2015
|
||||
==========================
|
||||
|
||||
* Initial release based on https://github.com/stvp/rollbar
|
||||
|
||||
22
vendor/github.com/jesseduffield/roll/LICENSE
generated
vendored
22
vendor/github.com/jesseduffield/roll/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2016 Stovepipe Studios
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
53
vendor/github.com/jesseduffield/roll/README.md
generated
vendored
53
vendor/github.com/jesseduffield/roll/README.md
generated
vendored
@@ -1,53 +0,0 @@
|
||||
roll
|
||||
----
|
||||
|
||||
`roll` is a basic Rollbar client for Go that reports errors and logs
|
||||
messages. It automatically builds stack traces and also supports
|
||||
arbitrary traces. All errors and messages are sent to Rollbar
|
||||
synchronously.
|
||||
|
||||
`roll` is intentionally simple. For more advanced functionality, check
|
||||
out [heroku/rollbar](https://github.com/heroku/rollbar).
|
||||
|
||||
[API docs on godoc.org](http://godoc.org/github.com/stvp/roll)
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
* Critical-, Error-, and Warning-level messages include a stack trace.
|
||||
However, Go's `error` type doesn't include stack information from the
|
||||
location the error was set or allocated. Instead, `roll` uses the
|
||||
stack information from where the error was reported.
|
||||
* Info- and Debug-level Rollbar messages do not include stack traces.
|
||||
* When calling `roll` away from where the error actually occurred,
|
||||
`roll`'s stack walking won't represent the actual stack trace at the
|
||||
time the error occurred. The `*Stack` variants of Critical, Error, and
|
||||
Warning take a `[]uintptr`, allowing the stack to be provided, rather
|
||||
than walked.
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
||||
`go test` will run tests against a fake server by default.
|
||||
|
||||
If the environment variable `TOKEN` is a Rollbar access token, running
|
||||
`go test` will produce errors using an environment named `test`.
|
||||
|
||||
TOKEN=f0df01587b8f76b2c217af34c479f9ea go test
|
||||
|
||||
Verify the reported errors manually in the Rollbar dashboard.
|
||||
|
||||
Contributors
|
||||
============
|
||||
|
||||
* @challiwill
|
||||
* @tysonmote
|
||||
* @apg
|
||||
|
||||
This library was forked from [stvp/rollbar](https://github.com/stvp/rollbar),
|
||||
which had contributions from:
|
||||
|
||||
* @kjk
|
||||
* @Soulou
|
||||
* @paulmach
|
||||
|
||||
242
vendor/github.com/jesseduffield/roll/client.go
generated
vendored
242
vendor/github.com/jesseduffield/roll/client.go
generated
vendored
@@ -1,242 +0,0 @@
|
||||
package roll
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/adler32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// By default, all Rollbar API requests are sent to this endpoint.
|
||||
endpoint = "https://api.rollbar.com/api/1/item/"
|
||||
|
||||
// Identify this Rollbar client library to the Rollbar API.
|
||||
clientName = "go-roll"
|
||||
clientVersion = "0.2.0"
|
||||
clientLanguage = "go"
|
||||
)
|
||||
|
||||
var (
|
||||
// Endpoint is the default HTTP(S) endpoint that all Rollbar API requests
|
||||
// will be sent to. By default, this is Rollbar's "Items" API endpoint. If
|
||||
// this is blank, no items will be sent to Rollbar.
|
||||
Endpoint = endpoint
|
||||
|
||||
// Rollbar access token for the global client. If this is blank, no items
|
||||
// will be sent to Rollbar.
|
||||
Token = ""
|
||||
|
||||
// Environment for all items reported with the global client.
|
||||
Environment = "development"
|
||||
)
|
||||
|
||||
type rollbarSuccess struct {
|
||||
Result map[string]string `json:"result"`
|
||||
}
|
||||
|
||||
// Client reports items to a single Rollbar project.
|
||||
type Client interface {
|
||||
Critical(err error, custom map[string]string) (uuid string, e error)
|
||||
CriticalStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error)
|
||||
Error(err error, custom map[string]string) (uuid string, e error)
|
||||
ErrorStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error)
|
||||
Warning(err error, custom map[string]string) (uuid string, e error)
|
||||
WarningStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error)
|
||||
Info(msg string, custom map[string]string) (uuid string, e error)
|
||||
Debug(msg string, custom map[string]string) (uuid string, e error)
|
||||
}
|
||||
|
||||
type rollbarClient struct {
|
||||
token string
|
||||
env string
|
||||
}
|
||||
|
||||
// New creates a new Rollbar client that reports items to the given project
|
||||
// token and with the given environment (eg. "production", "development", etc).
|
||||
func New(token, env string) Client {
|
||||
return &rollbarClient{token, env}
|
||||
}
|
||||
|
||||
func Critical(err error, custom map[string]string) (uuid string, e error) {
|
||||
return CriticalStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func CriticalStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
return New(Token, Environment).CriticalStack(err, ptrs, custom)
|
||||
}
|
||||
|
||||
func Error(err error, custom map[string]string) (uuid string, e error) {
|
||||
return ErrorStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func ErrorStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
return New(Token, Environment).ErrorStack(err, ptrs, custom)
|
||||
}
|
||||
|
||||
func Warning(err error, custom map[string]string) (uuid string, e error) {
|
||||
return WarningStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func WarningStack(err error, ptrs []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
return New(Token, Environment).WarningStack(err, ptrs, custom)
|
||||
}
|
||||
|
||||
func Info(msg string, custom map[string]string) (uuid string, e error) {
|
||||
return New(Token, Environment).Info(msg, custom)
|
||||
}
|
||||
|
||||
func Debug(msg string, custom map[string]string) (uuid string, e error) {
|
||||
return New(Token, Environment).Debug(msg, custom)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) Critical(err error, custom map[string]string) (uuid string, e error) {
|
||||
return c.CriticalStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) CriticalStack(err error, callers []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
item := c.buildTraceItem("critical", err, callers, custom)
|
||||
return c.send(item)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) Error(err error, custom map[string]string) (uuid string, e error) {
|
||||
return c.ErrorStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) ErrorStack(err error, callers []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
item := c.buildTraceItem("error", err, callers, custom)
|
||||
return c.send(item)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) Warning(err error, custom map[string]string) (uuid string, e error) {
|
||||
return c.WarningStack(err, getCallers(2), custom)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) WarningStack(err error, callers []uintptr, custom map[string]string) (uuid string, e error) {
|
||||
item := c.buildTraceItem("warning", err, callers, custom)
|
||||
return c.send(item)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) Info(msg string, custom map[string]string) (uuid string, e error) {
|
||||
item := c.buildMessageItem("info", msg, custom)
|
||||
return c.send(item)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) Debug(msg string, custom map[string]string) (uuid string, e error) {
|
||||
item := c.buildMessageItem("debug", msg, custom)
|
||||
return c.send(item)
|
||||
}
|
||||
|
||||
func (c *rollbarClient) buildTraceItem(level string, err error, callers []uintptr, custom map[string]string) (item map[string]interface{}) {
|
||||
stack := buildRollbarFrames(callers)
|
||||
item = c.buildItem(level, err.Error(), custom)
|
||||
itemData := item["data"].(map[string]interface{})
|
||||
itemData["fingerprint"] = stack.fingerprint()
|
||||
itemData["body"] = map[string]interface{}{
|
||||
"trace": map[string]interface{}{
|
||||
"frames": stack,
|
||||
"exception": map[string]interface{}{
|
||||
"class": errorClass(err),
|
||||
"message": err.Error(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func (c *rollbarClient) buildMessageItem(level string, msg string, custom map[string]string) (item map[string]interface{}) {
|
||||
item = c.buildItem(level, msg, custom)
|
||||
itemData := item["data"].(map[string]interface{})
|
||||
itemData["body"] = map[string]interface{}{
|
||||
"message": map[string]interface{}{
|
||||
"body": msg,
|
||||
},
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
func (c *rollbarClient) buildItem(level, title string, custom map[string]string) map[string]interface{} {
|
||||
hostname, _ := os.Hostname()
|
||||
|
||||
return map[string]interface{}{
|
||||
"access_token": c.token,
|
||||
"data": map[string]interface{}{
|
||||
"environment": c.env,
|
||||
"title": title,
|
||||
"level": level,
|
||||
"timestamp": time.Now().Unix(),
|
||||
"platform": runtime.GOOS,
|
||||
"language": clientLanguage,
|
||||
"server": map[string]interface{}{
|
||||
"host": hostname,
|
||||
},
|
||||
"notifier": map[string]interface{}{
|
||||
"name": clientName,
|
||||
"version": clientVersion,
|
||||
},
|
||||
"custom": custom,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// send reports the given item to Rollbar and returns either a UUID for the
|
||||
// reported item or an error.
|
||||
func (c *rollbarClient) send(item map[string]interface{}) (uuid string, err error) {
|
||||
if len(c.token) == 0 || len(Endpoint) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := http.Post(Endpoint, "application/json", bytes.NewReader(jsonBody))
|
||||
if err != nil {
|
||||
// If something goes wrong it really does not matter
|
||||
return "", nil
|
||||
}
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// If something goes wrong it really does not matter
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Extract UUID from JSON response
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
success := rollbarSuccess{}
|
||||
json.Unmarshal(body, &success)
|
||||
|
||||
return success.Result["uuid"], nil
|
||||
}
|
||||
|
||||
// errorClass returns a class name for an error (eg. "ErrUnexpectedEOF"). For
|
||||
// string errors, it returns an Adler-32 checksum of the error string.
|
||||
func errorClass(err error) string {
|
||||
class := reflect.TypeOf(err).String()
|
||||
if class == "" {
|
||||
return "panic"
|
||||
} else if class == "*errors.errorString" {
|
||||
checksum := adler32.Checksum([]byte(err.Error()))
|
||||
return fmt.Sprintf("{%x}", checksum)
|
||||
} else {
|
||||
return strings.TrimPrefix(class, "*")
|
||||
}
|
||||
}
|
||||
98
vendor/github.com/jesseduffield/roll/stack.go
generated
vendored
98
vendor/github.com/jesseduffield/roll/stack.go
generated
vendored
@@ -1,98 +0,0 @@
|
||||
package roll
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
knownFilePathPatterns = []string{
|
||||
"github.com/",
|
||||
"code.google.com/",
|
||||
"bitbucket.org/",
|
||||
"launchpad.net/",
|
||||
"gopkg.in/",
|
||||
}
|
||||
)
|
||||
|
||||
func getCallers(skip int) (pc []uintptr) {
|
||||
pc = make([]uintptr, 1000)
|
||||
i := runtime.Callers(skip+1, pc)
|
||||
return pc[0:i]
|
||||
}
|
||||
|
||||
// -- rollbarFrames
|
||||
|
||||
type rollbarFrame struct {
|
||||
Filename string `json:"filename"`
|
||||
Method string `json:"method"`
|
||||
Line int `json:"lineno"`
|
||||
}
|
||||
|
||||
type rollbarFrames []rollbarFrame
|
||||
|
||||
// buildRollbarFrames takes a slice of function pointers and returns a Rollbar
|
||||
// API payload containing the filename, method name, and line number of each
|
||||
// function.
|
||||
func buildRollbarFrames(callers []uintptr) (frames rollbarFrames) {
|
||||
frames = rollbarFrames{}
|
||||
|
||||
// 2016-08-24 - runtime.CallersFrames was added in Go 1.7, which should
|
||||
// replace the following code when roll is able to require Go 1.7+.
|
||||
for _, caller := range callers {
|
||||
frame := rollbarFrame{
|
||||
Filename: "???",
|
||||
Method: "???",
|
||||
}
|
||||
if fn := runtime.FuncForPC(caller); fn != nil {
|
||||
name, line := fn.FileLine(caller)
|
||||
frame.Filename = scrubFile(name)
|
||||
frame.Line = line
|
||||
frame.Method = scrubFunction(fn.Name())
|
||||
}
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
// fingerprint returns a checksum that uniquely identifies a stacktrace by the
|
||||
// filename, method name, and line number of every frame in the stack.
|
||||
func (f rollbarFrames) fingerprint() string {
|
||||
hash := crc32.NewIEEE()
|
||||
for _, frame := range f {
|
||||
fmt.Fprintf(hash, "%s%s%d", frame.Filename, frame.Method, frame.Line)
|
||||
}
|
||||
return fmt.Sprintf("%x", hash.Sum32())
|
||||
}
|
||||
|
||||
// -- Helpers
|
||||
|
||||
// scrubFile removes unneeded information from the path of a source file. This
|
||||
// makes them shorter in Rollbar UI as well as making them the same, regardless
|
||||
// of the machine the code was compiled on.
|
||||
//
|
||||
// Example:
|
||||
// /home/foo/go/src/github.com/stvp/roll/rollbar.go -> github.com/stvp/roll/rollbar.go
|
||||
func scrubFile(s string) string {
|
||||
var i int
|
||||
for _, pattern := range knownFilePathPatterns {
|
||||
i = strings.Index(s, pattern)
|
||||
if i != -1 {
|
||||
return s[i:]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// scrubFunction removes unneeded information from the full name of a function.
|
||||
//
|
||||
// Example:
|
||||
// github.com/stvp/roll.getCallers -> roll.getCallers
|
||||
func scrubFunction(name string) string {
|
||||
end := strings.LastIndex(name, string(os.PathSeparator))
|
||||
return name[end+1 : len(name)]
|
||||
}
|
||||
22
vendor/github.com/jesseduffield/rollrus/LICENSE
generated
vendored
22
vendor/github.com/jesseduffield/rollrus/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © Heroku 2014 - 2015
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
15
vendor/github.com/jesseduffield/rollrus/README.md
generated
vendored
15
vendor/github.com/jesseduffield/rollrus/README.md
generated
vendored
@@ -1,15 +0,0 @@
|
||||
[](https://travis-ci.org/heroku/rollrus) [](https://godoc.org/github.com/heroku/rollrus)
|
||||
|
||||
# What
|
||||
|
||||
Rollrus is what happens when [Logrus](https://github.com/sirupsen/logrus) meets [Roll](https://github.com/stvp/roll).
|
||||
|
||||
When a .Error, .Fatal or .Panic logging function is called, report the details to rollbar via a Logrus hook.
|
||||
|
||||
Delivery is synchronous to help ensure that logs are delivered.
|
||||
|
||||
If the error includes a [`StackTrace`](https://godoc.org/github.com/pkg/errors#StackTrace), that `StackTrace` is reported to rollbar.
|
||||
|
||||
# Usage
|
||||
|
||||
Examples available in the [tests](https://github.com/heroku/rollrus/blob/master/rollrus_test.go) or on [GoDoc](https://godoc.org/github.com/heroku/rollrus).
|
||||
7
vendor/github.com/jesseduffield/rollrus/go.mod
generated
vendored
7
vendor/github.com/jesseduffield/rollrus/go.mod
generated
vendored
@@ -1,7 +0,0 @@
|
||||
module github.com/jesseduffield/rollrus
|
||||
|
||||
require (
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.3.0
|
||||
)
|
||||
19
vendor/github.com/jesseduffield/rollrus/go.sum
generated
vendored
19
vendor/github.com/jesseduffield/rollrus/go.sum
generated
vendored
@@ -1,19 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00/go.mod h1:cWNQljQAWYBp4wchyGfql4q2jRNZXxiE1KhVQgz+JaM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
287
vendor/github.com/jesseduffield/rollrus/rollrus.go
generated
vendored
287
vendor/github.com/jesseduffield/rollrus/rollrus.go
generated
vendored
@@ -1,287 +0,0 @@
|
||||
// Package rollrus combines github.com/jesseduffield/roll with github.com/sirupsen/logrus
|
||||
// via logrus.Hook mechanism, so that whenever logrus' logger.Error/f(),
|
||||
// logger.Fatal/f() or logger.Panic/f() are used the messages are
|
||||
// intercepted and sent to rollbar.
|
||||
//
|
||||
// Using SetupLogging should suffice for basic use cases that use the logrus
|
||||
// singleton logger.
|
||||
//
|
||||
// More custom uses are supported by creating a new Hook with NewHook and
|
||||
// registering that hook with the logrus Logger of choice.
|
||||
//
|
||||
// The levels can be customized with the WithLevels OptionFunc.
|
||||
//
|
||||
// Specific errors can be ignored with the WithIgnoredErrors OptionFunc. This is
|
||||
// useful for ignoring errors such as context.Canceled.
|
||||
//
|
||||
// See the Examples in the tests for more usage.
|
||||
package rollrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/roll"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var defaultTriggerLevels = []logrus.Level{
|
||||
logrus.ErrorLevel,
|
||||
logrus.FatalLevel,
|
||||
logrus.PanicLevel,
|
||||
}
|
||||
|
||||
// Hook is a wrapper for the rollbar Client and is usable as a logrus.Hook.
|
||||
type Hook struct {
|
||||
roll.Client
|
||||
triggers []logrus.Level
|
||||
ignoredErrors []error
|
||||
ignoreErrorFunc func(error) bool
|
||||
ignoreFunc func(error, map[string]string) bool
|
||||
|
||||
// only used for tests to verify whether or not a report happened.
|
||||
reported bool
|
||||
}
|
||||
|
||||
// OptionFunc that can be passed to NewHook.
|
||||
type OptionFunc func(*Hook)
|
||||
|
||||
// wellKnownErrorFields are the names of the fields to be checked for values of
|
||||
// type `error`, in priority order.
|
||||
var wellKnownErrorFields = []string{
|
||||
logrus.ErrorKey, "err",
|
||||
}
|
||||
|
||||
// WithLevels is an OptionFunc that customizes the log.Levels the hook will
|
||||
// report on.
|
||||
func WithLevels(levels ...logrus.Level) OptionFunc {
|
||||
return func(h *Hook) {
|
||||
h.triggers = levels
|
||||
}
|
||||
}
|
||||
|
||||
// WithMinLevel is an OptionFunc that customizes the log.Levels the hook will
|
||||
// report on by selecting all levels more severe than the one provided.
|
||||
func WithMinLevel(level logrus.Level) OptionFunc {
|
||||
var levels []logrus.Level
|
||||
for _, l := range logrus.AllLevels {
|
||||
if l <= level {
|
||||
levels = append(levels, l)
|
||||
}
|
||||
}
|
||||
|
||||
return func(h *Hook) {
|
||||
h.triggers = levels
|
||||
}
|
||||
}
|
||||
|
||||
// WithIgnoredErrors is an OptionFunc that whitelists certain errors to prevent
|
||||
// them from firing. See https://golang.org/ref/spec#Comparison_operators
|
||||
func WithIgnoredErrors(errors ...error) OptionFunc {
|
||||
return func(h *Hook) {
|
||||
h.ignoredErrors = append(h.ignoredErrors, errors...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithIgnoreErrorFunc is an OptionFunc that receives the error that is about
|
||||
// to be logged and returns true/false if it wants to fire a rollbar alert for.
|
||||
func WithIgnoreErrorFunc(fn func(error) bool) OptionFunc {
|
||||
return func(h *Hook) {
|
||||
h.ignoreErrorFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithIgnoreFunc is an OptionFunc that receives the error and custom fields that are about
|
||||
// to be logged and returns true/false if it wants to fire a rollbar alert for.
|
||||
func WithIgnoreFunc(fn func(err error, fields map[string]string) bool) OptionFunc {
|
||||
return func(h *Hook) {
|
||||
h.ignoreFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
// NewHook creates a hook that is intended for use with your own logrus.Logger
|
||||
// instance. Uses the defualt report levels defined in wellKnownErrorFields.
|
||||
func NewHook(token string, env string, opts ...OptionFunc) *Hook {
|
||||
h := NewHookForLevels(token, env, defaultTriggerLevels)
|
||||
|
||||
for _, o := range opts {
|
||||
o(h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// NewHookForLevels provided by the caller. Otherwise works like NewHook.
|
||||
func NewHookForLevels(token string, env string, levels []logrus.Level) *Hook {
|
||||
return &Hook{
|
||||
Client: roll.New(token, env),
|
||||
triggers: levels,
|
||||
ignoredErrors: make([]error, 0),
|
||||
ignoreErrorFunc: func(error) bool { return false },
|
||||
ignoreFunc: func(error, map[string]string) bool { return false },
|
||||
}
|
||||
}
|
||||
|
||||
// SetupLogging for use on Heroku. If token is not an empty string a rollbar
|
||||
// hook is added with the environment set to env. The log formatter is set to a
|
||||
// TextFormatter with timestamps disabled.
|
||||
func SetupLogging(token, env string) {
|
||||
setupLogging(token, env, defaultTriggerLevels)
|
||||
}
|
||||
|
||||
// SetupLoggingForLevels works like SetupLogging, but allows you to
|
||||
// set the levels on which to trigger this hook.
|
||||
func SetupLoggingForLevels(token, env string, levels []logrus.Level) {
|
||||
setupLogging(token, env, levels)
|
||||
}
|
||||
|
||||
func setupLogging(token, env string, levels []logrus.Level) {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{DisableTimestamp: true})
|
||||
|
||||
if token != "" {
|
||||
logrus.AddHook(NewHookForLevels(token, env, levels))
|
||||
}
|
||||
}
|
||||
|
||||
// ReportPanic attempts to report the panic to rollbar using the provided
|
||||
// client and then re-panic. If it can't report the panic it will print an
|
||||
// error to stderr.
|
||||
func (r *Hook) ReportPanic() {
|
||||
if p := recover(); p != nil {
|
||||
if _, err := r.Client.Critical(fmt.Errorf("panic: %q", p), nil); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "reporting_panic=false err=%q\n", err)
|
||||
}
|
||||
panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
// ReportPanic attempts to report the panic to rollbar if the token is set
|
||||
func ReportPanic(token, env string) {
|
||||
if token != "" {
|
||||
h := &Hook{Client: roll.New(token, env)}
|
||||
h.ReportPanic()
|
||||
}
|
||||
}
|
||||
|
||||
// Levels returns the logrus log.Levels that this hook handles
|
||||
func (r *Hook) Levels() []logrus.Level {
|
||||
if r.triggers == nil {
|
||||
return defaultTriggerLevels
|
||||
}
|
||||
return r.triggers
|
||||
}
|
||||
|
||||
// Fire the hook. This is called by Logrus for entries that match the levels
|
||||
// returned by Levels().
|
||||
func (r *Hook) Fire(entry *logrus.Entry) error {
|
||||
trace, cause := extractError(entry)
|
||||
for _, ie := range r.ignoredErrors {
|
||||
if ie == cause {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if r.ignoreErrorFunc(cause) {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := convertFields(entry.Data)
|
||||
if _, exists := m["time"]; !exists {
|
||||
m["time"] = entry.Time.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if r.ignoreFunc(cause, m) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.report(entry, cause, m, trace)
|
||||
}
|
||||
|
||||
func (r *Hook) report(entry *logrus.Entry, cause error, m map[string]string, trace []uintptr) (err error) {
|
||||
hasTrace := len(trace) > 0
|
||||
level := entry.Level
|
||||
|
||||
r.reported = true
|
||||
|
||||
switch {
|
||||
case hasTrace && level == logrus.FatalLevel:
|
||||
_, err = r.Client.CriticalStack(cause, trace, m)
|
||||
case hasTrace && level == logrus.PanicLevel:
|
||||
_, err = r.Client.CriticalStack(cause, trace, m)
|
||||
case hasTrace && level == logrus.ErrorLevel:
|
||||
_, err = r.Client.ErrorStack(cause, trace, m)
|
||||
case hasTrace && level == logrus.WarnLevel:
|
||||
_, err = r.Client.WarningStack(cause, trace, m)
|
||||
case level == logrus.FatalLevel || level == logrus.PanicLevel:
|
||||
_, err = r.Client.Critical(cause, m)
|
||||
case level == logrus.ErrorLevel:
|
||||
_, err = r.Client.Error(cause, m)
|
||||
case level == logrus.WarnLevel:
|
||||
_, err = r.Client.Warning(cause, m)
|
||||
case level == logrus.InfoLevel:
|
||||
_, err = r.Client.Info(entry.Message, m)
|
||||
case level == logrus.DebugLevel:
|
||||
_, err = r.Client.Debug(entry.Message, m)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// convertFields converts from log.Fields to map[string]string so that we can
|
||||
// report extra fields to Rollbar
|
||||
func convertFields(fields logrus.Fields) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range fields {
|
||||
switch t := v.(type) {
|
||||
case time.Time:
|
||||
m[k] = t.Format(time.RFC3339)
|
||||
default:
|
||||
if s, ok := v.(fmt.Stringer); ok {
|
||||
m[k] = s.String()
|
||||
} else {
|
||||
m[k] = fmt.Sprintf("%+v", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// extractError attempts to extract an error from a well known field, err or error
|
||||
func extractError(entry *logrus.Entry) ([]uintptr, error) {
|
||||
var trace []uintptr
|
||||
fields := entry.Data
|
||||
|
||||
type stackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
|
||||
for _, f := range wellKnownErrorFields {
|
||||
e, ok := fields[f]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
cause := errors.Cause(err)
|
||||
tracer, ok := err.(stackTracer)
|
||||
if ok {
|
||||
return copyStackTrace(tracer.StackTrace()), cause
|
||||
}
|
||||
return trace, cause
|
||||
}
|
||||
|
||||
// when no error found, default to the logged message.
|
||||
return trace, fmt.Errorf(entry.Message)
|
||||
}
|
||||
|
||||
func copyStackTrace(trace errors.StackTrace) (out []uintptr) {
|
||||
for _, frame := range trace {
|
||||
out = append(out, uintptr(frame))
|
||||
}
|
||||
return
|
||||
}
|
||||
39
vendor/github.com/jesseduffield/termbox-go/api.go
generated
vendored
39
vendor/github.com/jesseduffield/termbox-go/api.go
generated
vendored
@@ -2,13 +2,16 @@
|
||||
|
||||
package termbox
|
||||
|
||||
import "github.com/mattn/go-runewidth"
|
||||
import "fmt"
|
||||
import "os"
|
||||
import "os/signal"
|
||||
import "syscall"
|
||||
import "runtime"
|
||||
import "time"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// public API
|
||||
|
||||
@@ -26,13 +29,21 @@ func Init() error {
|
||||
|
||||
var err error
|
||||
|
||||
out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" {
|
||||
out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in = int(out.Fd())
|
||||
} else {
|
||||
out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = setup_term()
|
||||
|
||||
39
vendor/github.com/jesseduffield/termbox-go/syscalls.go
generated
vendored
39
vendor/github.com/jesseduffield/termbox-go/syscalls.go
generated
vendored
@@ -1,39 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package termbox
|
||||
|
||||
/*
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type syscall_Termios C.struct_termios
|
||||
|
||||
const (
|
||||
syscall_IGNBRK = C.IGNBRK
|
||||
syscall_BRKINT = C.BRKINT
|
||||
syscall_PARMRK = C.PARMRK
|
||||
syscall_ISTRIP = C.ISTRIP
|
||||
syscall_INLCR = C.INLCR
|
||||
syscall_IGNCR = C.IGNCR
|
||||
syscall_ICRNL = C.ICRNL
|
||||
syscall_IXON = C.IXON
|
||||
syscall_OPOST = C.OPOST
|
||||
syscall_ECHO = C.ECHO
|
||||
syscall_ECHONL = C.ECHONL
|
||||
syscall_ICANON = C.ICANON
|
||||
syscall_ISIG = C.ISIG
|
||||
syscall_IEXTEN = C.IEXTEN
|
||||
syscall_CSIZE = C.CSIZE
|
||||
syscall_PARENB = C.PARENB
|
||||
syscall_CS8 = C.CS8
|
||||
syscall_VMIN = C.VMIN
|
||||
syscall_VTIME = C.VTIME
|
||||
|
||||
// on darwin change these to (on *bsd too?):
|
||||
// C.TIOCGETA
|
||||
// C.TIOCSETA
|
||||
syscall_TCGETS = C.TCGETS
|
||||
syscall_TCSETS = C.TCSETS
|
||||
)
|
||||
1
vendor/github.com/konsorten/go-windows-terminal-sequences/README.md
generated
vendored
1
vendor/github.com/konsorten/go-windows-terminal-sequences/README.md
generated
vendored
@@ -26,6 +26,7 @@ The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
|
||||
We thank all the authors who provided code to this library:
|
||||
|
||||
* Felix Kollmann
|
||||
* Nicolas Perraut
|
||||
|
||||
## License
|
||||
|
||||
|
||||
11
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go
generated
vendored
Normal file
11
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_dummy.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build linux darwin
|
||||
|
||||
package sequences
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
|
||||
return fmt.Errorf("windows only package")
|
||||
}
|
||||
4
vendor/github.com/mattn/go-isatty/go.mod
generated
vendored
4
vendor/github.com/mattn/go-isatty/go.mod
generated
vendored
@@ -1,5 +1,5 @@
|
||||
module github.com/mattn/go-isatty
|
||||
|
||||
require golang.org/x/sys v0.0.0-20191008105621-543471e840be
|
||||
go 1.12
|
||||
|
||||
go 1.14
|
||||
require golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
||||
|
||||
6
vendor/github.com/mattn/go-isatty/go.sum
generated
vendored
6
vendor/github.com/mattn/go-isatty/go.sum
generated
vendored
@@ -1,4 +1,2 @@
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
2
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
2
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(fd)
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ go-runewidth
|
||||
============
|
||||
|
||||
[](https://travis-ci.org/mattn/go-runewidth)
|
||||
[](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD)
|
||||
[](https://codecov.io/gh/mattn/go-runewidth)
|
||||
[](http://godoc.org/github.com/mattn/go-runewidth)
|
||||
[](https://goreportcard.com/report/github.com/mattn/go-runewidth)
|
||||
|
||||
12
vendor/github.com/mattn/go-runewidth/go.test.sh
generated
vendored
Normal file
12
vendor/github.com/mattn/go-runewidth/go.test.sh
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
||||
14
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
14
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@@ -50,7 +50,6 @@ func inTables(r rune, ts ...table) bool {
|
||||
}
|
||||
|
||||
func inTable(r rune, t table) bool {
|
||||
// func (t table) IncludesRune(r rune) bool {
|
||||
if r < t[0].first {
|
||||
return false
|
||||
}
|
||||
@@ -102,11 +101,9 @@ func NewCondition() *Condition {
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func (c *Condition) RuneWidth(r rune) int {
|
||||
switch {
|
||||
case r < 0 || r > 0x10FFFF ||
|
||||
inTables(r, nonprint, combining, notassigned):
|
||||
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
|
||||
return 0
|
||||
case (c.EastAsianWidth && IsAmbiguousWidth(r)) ||
|
||||
inTables(r, doublewidth, emoji):
|
||||
case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
@@ -128,9 +125,12 @@ func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
|
||||
}
|
||||
w := c.RuneWidth(r)
|
||||
if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
|
||||
w = 0
|
||||
if width < w {
|
||||
width = w
|
||||
}
|
||||
} else {
|
||||
width += w
|
||||
}
|
||||
width += w
|
||||
r1, r2 = r2, r
|
||||
}
|
||||
return width
|
||||
|
||||
2
vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
2
vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// Code generated by script/generate.go. DO NOT EDIT.
|
||||
|
||||
package runewidth
|
||||
|
||||
var combining = table{
|
||||
|
||||
3
vendor/github.com/pelletier/go-toml/.gitignore
generated
vendored
3
vendor/github.com/pelletier/go-toml/.gitignore
generated
vendored
@@ -1,2 +1,5 @@
|
||||
test_program/test_program_bin
|
||||
fuzz/
|
||||
cmd/tomll/tomll
|
||||
cmd/tomljson/tomljson
|
||||
cmd/tomltestgen/tomltestgen
|
||||
|
||||
132
vendor/github.com/pelletier/go-toml/CONTRIBUTING.md
generated
vendored
Normal file
132
vendor/github.com/pelletier/go-toml/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
## Contributing
|
||||
|
||||
Thank you for your interest in go-toml! We appreciate you considering
|
||||
contributing to go-toml!
|
||||
|
||||
The main goal is the project is to provide an easy-to-use TOML
|
||||
implementation for Go that gets the job done and gets out of your way –
|
||||
dealing with TOML is probably not the central piece of your project.
|
||||
|
||||
As the single maintainer of go-toml, time is scarce. All help, big or
|
||||
small, is more than welcomed!
|
||||
|
||||
### Ask questions
|
||||
|
||||
Any question you may have, somebody else might have it too. Always feel
|
||||
free to ask them on the [issues tracker][issues-tracker]. We will try to
|
||||
answer them as clearly and quickly as possible, time permitting.
|
||||
|
||||
Asking questions also helps us identify areas where the documentation needs
|
||||
improvement, or new features that weren't envisioned before. Sometimes, a
|
||||
seemingly innocent question leads to the fix of a bug. Don't hesitate and
|
||||
ask away!
|
||||
|
||||
### Improve the documentation
|
||||
|
||||
The best way to share your knowledge and experience with go-toml is to
|
||||
improve the documentation. Fix a typo, clarify an interface, add an
|
||||
example, anything goes!
|
||||
|
||||
The documentation is present in the [README][readme] and thorough the
|
||||
source code. On release, it gets updated on [GoDoc][godoc]. To make a
|
||||
change to the documentation, create a pull request with your proposed
|
||||
changes. For simple changes like that, the easiest way to go is probably
|
||||
the "Fork this project and edit the file" button on Github, displayed at
|
||||
the top right of the file. Unless it's a trivial change (for example a
|
||||
typo), provide a little bit of context in your pull request description or
|
||||
commit message.
|
||||
|
||||
### Report a bug
|
||||
|
||||
Found a bug! Sorry to hear that :(. Help us and other track them down and
|
||||
fix by reporting it. [File a new bug report][bug-report] on the [issues
|
||||
tracker][issues-tracker]. The template should provide enough guidance on
|
||||
what to include. When in doubt: add more details! By reducing ambiguity and
|
||||
providing more information, it decreases back and forth and saves everyone
|
||||
time.
|
||||
|
||||
### Code changes
|
||||
|
||||
Want to contribute a patch? Very happy to hear that!
|
||||
|
||||
First, some high-level rules:
|
||||
|
||||
* A short proposal with some POC code is better than a lengthy piece of
|
||||
text with no code. Code speaks louder than words.
|
||||
* No backward-incompatible patch will be accepted unless discussed.
|
||||
Sometimes it's hard, and Go's lack of versioning by default does not
|
||||
help, but we try not to break people's programs unless we absolutely have
|
||||
to.
|
||||
* If you are writing a new feature or extending an existing one, make sure
|
||||
to write some documentation.
|
||||
* Bug fixes need to be accompanied with regression tests.
|
||||
* New code needs to be tested.
|
||||
* Your commit messages need to explain why the change is needed, even if
|
||||
already included in the PR description.
|
||||
|
||||
It does sound like a lot, but those best practices are here to save time
|
||||
overall and continuously improve the quality of the project, which is
|
||||
something everyone benefits from.
|
||||
|
||||
#### Get started
|
||||
|
||||
The fairly standard code contribution process looks like that:
|
||||
|
||||
1. [Fork the project][fork].
|
||||
2. Make your changes, commit on any branch you like.
|
||||
3. [Open up a pull request][pull-request]
|
||||
4. Review, potential ask for changes.
|
||||
5. Merge. You're in!
|
||||
|
||||
Feel free to ask for help! You can create draft pull requests to gather
|
||||
some early feedback!
|
||||
|
||||
#### Run the tests
|
||||
|
||||
You can run tests for go-toml using Go's test tool: `go test ./...`.
|
||||
When creating a pull requests, all tests will be ran on Linux on a few Go
|
||||
versions (Travis CI), and on Windows using the latest Go version
|
||||
(AppVeyor).
|
||||
|
||||
#### Style
|
||||
|
||||
Try to look around and follow the same format and structure as the rest of
|
||||
the code. We enforce using `go fmt` on the whole code base.
|
||||
|
||||
---
|
||||
|
||||
### Maintainers-only
|
||||
|
||||
#### Merge pull request
|
||||
|
||||
Checklist:
|
||||
|
||||
* Passing CI.
|
||||
* Does not introduce backward-incompatible changes (unless discussed).
|
||||
* Has relevant doc changes.
|
||||
* Has relevant unit tests.
|
||||
|
||||
1. Merge using "squash and merge".
|
||||
2. Make sure to edit the commit message to keep all the useful information
|
||||
nice and clean.
|
||||
3. Make sure the commit title is clear and contains the PR number (#123).
|
||||
|
||||
#### New release
|
||||
|
||||
1. Go to [releases][releases]. Click on "X commits to master since this
|
||||
release".
|
||||
2. Make note of all the changes. Look for backward incompatible changes,
|
||||
new features, and bug fixes.
|
||||
3. Pick the new version using the above and semver.
|
||||
4. Create a [new release][new-release].
|
||||
5. Follow the same format as [1.1.0][release-110].
|
||||
|
||||
[issues-tracker]: https://github.com/pelletier/go-toml/issues
|
||||
[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md
|
||||
[godoc]: https://godoc.org/github.com/pelletier/go-toml
|
||||
[readme]: ./README.md
|
||||
[fork]: https://help.github.com/articles/fork-a-repo
|
||||
[pull-request]: https://help.github.com/en/articles/creating-a-pull-request
|
||||
[releases]: https://github.com/pelletier/go-toml/releases
|
||||
[new-release]: https://github.com/pelletier/go-toml/releases/new
|
||||
[release-110]: https://github.com/pelletier/go-toml/releases/tag/v1.1.0
|
||||
11
vendor/github.com/pelletier/go-toml/Dockerfile
generated
vendored
Normal file
11
vendor/github.com/pelletier/go-toml/Dockerfile
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM golang:1.12-alpine3.9 as builder
|
||||
WORKDIR /go/src/github.com/pelletier/go-toml
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=linux
|
||||
RUN go install ./...
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /go/bin/tomll /usr/bin/tomll
|
||||
COPY --from=builder /go/bin/tomljson /usr/bin/tomljson
|
||||
COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml
|
||||
5
vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
5
vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**Issue:** add link to pelletier/go-toml issue here
|
||||
|
||||
Explanation of what this pull request does.
|
||||
|
||||
More detailed description of the decisions being made and the reasons why (if the patch is non-trivial).
|
||||
38
vendor/github.com/pelletier/go-toml/README.md
generated
vendored
38
vendor/github.com/pelletier/go-toml/README.md
generated
vendored
@@ -3,13 +3,14 @@
|
||||
Go library for the [TOML](https://github.com/mojombo/toml) format.
|
||||
|
||||
This library supports TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
[v0.5.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md)
|
||||
|
||||
[](http://godoc.org/github.com/pelletier/go-toml)
|
||||
[](https://github.com/pelletier/go-toml/blob/master/LICENSE)
|
||||
[](https://travis-ci.org/pelletier/go-toml)
|
||||
[](https://coveralls.io/github/pelletier/go-toml?branch=master)
|
||||
[](https://dev.azure.com/pelletierthomas/go-toml-ci/_build/latest?definitionId=1&branchName=master)
|
||||
[](https://codecov.io/gh/pelletier/go-toml)
|
||||
[](https://goreportcard.com/report/github.com/pelletier/go-toml)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml?ref=badge_shield)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -98,6 +99,30 @@ Go-toml provides two handy command line tools:
|
||||
go install github.com/pelletier/go-toml/cmd/tomljson
|
||||
tomljson --help
|
||||
```
|
||||
|
||||
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
|
||||
|
||||
```
|
||||
go install github.com/pelletier/go-toml/cmd/jsontoml
|
||||
jsontoml --help
|
||||
```
|
||||
|
||||
### Docker image
|
||||
|
||||
Those tools are also availble as a Docker image from
|
||||
[dockerhub](https://hub.docker.com/r/pelletier/go-toml). For example, to
|
||||
use `tomljson`:
|
||||
|
||||
```
|
||||
docker run -v $PWD:/workdir pelletier/go-toml tomljson /workdir/example.toml
|
||||
```
|
||||
|
||||
Only master (`latest`) and tagged versions are published to dockerhub. You
|
||||
can build your own image as usual:
|
||||
|
||||
```
|
||||
docker build -t go-toml .
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -107,12 +132,7 @@ much appreciated!
|
||||
|
||||
### Run tests
|
||||
|
||||
You have to make sure two kind of tests run:
|
||||
|
||||
1. The Go unit tests
|
||||
2. The TOML examples base
|
||||
|
||||
You can run both of them using `./test.sh`.
|
||||
`go test ./...`
|
||||
|
||||
### Fuzzing
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user