mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 10:16:22 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb7929a392 | ||
|
|
e6ee73515d | ||
|
|
f1568194d5 | ||
|
|
0cc23ffc69 | ||
|
|
e3abca6d65 | ||
|
|
282f851f38 | ||
|
|
1c7251b8ca | ||
|
|
7ec23ece47 |
@@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.14
|
||||
- image: circleci/golang:1.13
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.14
|
||||
- image: circleci/golang:1.13
|
||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Bump Homebrew formula
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get tag
|
||||
id: tag
|
||||
run: echo "::set-output name=tag::${GITHUB_REF##*/}"
|
||||
- name: Bump Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v1.1.0
|
||||
with:
|
||||
token: ${{secrets.GITHUB_API_TOKEN}}
|
||||
formula: lazygit
|
||||
url: "https://github.com/${{github.repository}}/archive/${{steps.tag.outputs.tag}}.tar.gz"
|
||||
+30
-31
@@ -17,16 +17,16 @@ builds:
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: 32-bit
|
||||
amd64: x86_64
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
archive:
|
||||
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:
|
||||
@@ -38,28 +38,27 @@ changelog:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
- '^bump'
|
||||
brews:
|
||||
-
|
||||
# Reporitory to push the tap to.
|
||||
github:
|
||||
owner: jesseduffield
|
||||
name: homebrew-lazygit
|
||||
brew:
|
||||
# Reporitory to push the tap to.
|
||||
github:
|
||||
owner: jesseduffield
|
||||
name: homebrew-lazygit
|
||||
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: 'https://github.com/jesseduffield/lazygit/'
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: 'https://github.com/jesseduffield/lazygit/'
|
||||
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: 'A simple terminal UI for git commands, written in Go'
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: 'A simple terminal UI for git commands, written in Go'
|
||||
|
||||
# # Packages your package depends on.
|
||||
# dependencies:
|
||||
# - git
|
||||
# - zsh
|
||||
# # Packages that conflict with your package.
|
||||
# conflicts:
|
||||
# - svn
|
||||
# - bash
|
||||
# # Packages your package depends on.
|
||||
# dependencies:
|
||||
# - git
|
||||
# - zsh
|
||||
# # Packages that conflict with your package.
|
||||
# conflicts:
|
||||
# - svn
|
||||
# - bash
|
||||
|
||||
# test comment to see if goreleaser only releases on new commits
|
||||
# test comment to see if goreleaser only releases on new commits
|
||||
|
||||
+2
-2
@@ -2,12 +2,12 @@
|
||||
# docker build -t lazygit .
|
||||
# docker run -it lazygit:latest /bin/sh -l
|
||||
|
||||
FROM golang:1.14-alpine3.11
|
||||
FROM golang:1.13-alpine3.10
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY ./ .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build
|
||||
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.10
|
||||
RUN apk add -U git xdg-utils
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY --from=0 /go/src/github.com/jesseduffield/lazygit /go/src/github.com/jesseduffield/lazygit
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []() [](https://www.tickgit.com/browse?repo=github.com/jesseduffield/lazygit)
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []()
|
||||
|
||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui 'gocui') library.
|
||||
|
||||
Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? *Are you kidding me?* To stage part of a file you need to use a command line program stepping through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, bad luck? *Are you KIDDING me?!* Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? *YOU HAVE GOT TO BE KIDDING ME!*
|
||||
Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? *Are you kidding me?* To stage part of a file you need to use a command line program stepping through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, bad luck? *Are you KIDDING me?!* Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? *YOU HAVE GOT TO BE KIDDING ME!*
|
||||
|
||||
If you're a mere mortal like me and you're tired of hearing how powerful git is when in your daily life it's a powerful pain in your ass, lazygit might be for you.
|
||||
|
||||
@@ -25,32 +25,17 @@ Github Sponsors is matching all donations dollar-for-dollar for 12 months so if
|
||||
|
||||
### Homebrew
|
||||
|
||||
Normally the lazygit formula can be found in the Homebrew core but we suggest you tap our formula to get the frequently updated one. It works with Linux, too.
|
||||
|
||||
Tap:
|
||||
|
||||
```
|
||||
brew install jesseduffield/lazygit/lazygit
|
||||
```
|
||||
|
||||
Core:
|
||||
|
||||
```
|
||||
```sh
|
||||
brew install lazygit
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
|
||||
Latest version built from github releases.
|
||||
Tap:
|
||||
|
||||
```
|
||||
sudo port install lazygit
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
Packages for Ubuntu are available via [Launchpad PPA](https://launchpad.net/~lazygit-team).
|
||||
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.
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:lazygit-team/release
|
||||
@@ -58,6 +43,16 @@ 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
|
||||
@@ -67,17 +62,6 @@ They follow upstream latest releases
|
||||
```sh
|
||||
sudo xbps-install -S lazygit
|
||||
```
|
||||
### Scoop (Windows)
|
||||
|
||||
You can install `lazygit` using [scoop](https://scoop.sh/). It's in the `extras` bucket:
|
||||
|
||||
```sh
|
||||
# Add the extras bucket
|
||||
scoop bucket add extras
|
||||
|
||||
# Install lazygit
|
||||
scoop install lazygit
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
|
||||
@@ -86,24 +70,15 @@ Packages for Arch Linux are available via AUR (Arch User Repository).
|
||||
There are two packages. The stable one which is built with the latest release
|
||||
and the git version which builds from the most recent commit.
|
||||
|
||||
- Stable: <https://aur.archlinux.org/packages/lazygit/>
|
||||
- Development: <https://aur.archlinux.org/packages/lazygit-git/>
|
||||
- Stable: https://aur.archlinux.org/packages/lazygit/
|
||||
- Development: https://aur.archlinux.org/packages/lazygit-git/
|
||||
|
||||
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
|
||||
```
|
||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
|
||||
### Conda
|
||||
|
||||
Released versions are available for different platforms, see <https://anaconda.org/conda-forge/lazygit>
|
||||
Released versions are available for different platforms, see https://anaconda.org/conda-forge/lazygit
|
||||
|
||||
```sh
|
||||
conda install -c conda-forge lazygit
|
||||
@@ -136,34 +111,6 @@ whichever rc file you're using).
|
||||
- List of keybindings
|
||||
[here](/docs/keybindings).
|
||||
|
||||
## Changing Directory On Exit
|
||||
|
||||
If you change repos in lazygit and want your shell to change directory into that repo on exiting lazygit, add this to your `~/.zshrc` (or other rc file):
|
||||
|
||||
```
|
||||
lg()
|
||||
{
|
||||
export LAZYGIT_NEW_DIR_FILE=~/.lazygit/newdir
|
||||
|
||||
lazygit "$@"
|
||||
|
||||
if [ -f $LAZYGIT_NEW_DIR_FILE ]; then
|
||||
cd "$(cat $LAZYGIT_NEW_DIR_FILE)"
|
||||
rm -f $LAZYGIT_NEW_DIR_FILE > /dev/null
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch directories to whatever you were in inside lazyigt. To override this behaviour you can exit using `shift+Q` rather than just `q`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Check the [configuration docs](docs/Config.md).
|
||||
|
||||
## Custom Pagers
|
||||
|
||||
See the [docs](docs/Custom_Pagers.md)
|
||||
|
||||
## Cool features
|
||||
|
||||
- Adding files easily
|
||||
|
||||
+11
-170
@@ -1,37 +1,25 @@
|
||||
# User Config
|
||||
# User Config:
|
||||
|
||||
Default path for the config file:
|
||||
|
||||
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||
|
||||
## Default
|
||||
## Default:
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
# stuff relating to the UI
|
||||
scrollHeight: 2 # how many lines you scroll by
|
||||
scrollPastBottom: true # enable scrolling past the bottom
|
||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
- white
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- green
|
||||
- white
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
@@ -42,128 +30,32 @@ Default path for the config file:
|
||||
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
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
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: ':'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
refresh: 'R'
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
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
|
||||
## Platform Defaults:
|
||||
|
||||
### Windows
|
||||
### Windows:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||
```
|
||||
|
||||
### Linux
|
||||
### Linux:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
```
|
||||
|
||||
### OSX
|
||||
### OSX:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Recommended Config Values
|
||||
### Recommended Config Values:
|
||||
|
||||
for users of VSCode
|
||||
|
||||
@@ -172,7 +64,7 @@ for users of VSCode
|
||||
openCommand: 'code -r {{filename}}'
|
||||
```
|
||||
|
||||
## Color Attributes
|
||||
## Color Attributes:
|
||||
|
||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||
The available attributes are:
|
||||
@@ -190,7 +82,7 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme
|
||||
## Light terminal theme:
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
@@ -203,59 +95,8 @@ If you have issues with a light terminal theme where you can't read / see the te
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
```
|
||||
|
||||
## Example Coloring
|
||||
## 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'
|
||||
nextMatch: '='
|
||||
prevMatch: '-'
|
||||
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'
|
||||
```
|
||||
|
||||
## Custom pull request URLs
|
||||
|
||||
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and
|
||||
the web interface/API itself. To work with those, Lazygit needs to know where it needs to create
|
||||
the pull request. You can do so on your `config.yml` file using the following syntax:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
"<gitDomain>": "<provider>:<webDomain>"
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com`
|
||||
- `provider` is one of `github`, `bitbucket` or `gitlab`
|
||||
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Custom Pagers
|
||||
|
||||
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing 'o' in the Status panel).
|
||||
|
||||
Support does not extend to windows users, because we're making use of a package which doesn't have windows support.
|
||||
|
||||
## Default:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
```
|
||||
|
||||
the `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to always, others want it set to 'never'.
|
||||
|
||||
## Delta:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
pager: delta --dark --paging=never --24-bit-color=never
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Diff-so-fancy
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
pager: diff-so-fancy
|
||||
```
|
||||
|
||||

|
||||
|
||||
## ydiff
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
|
||||
git:
|
||||
paging:
|
||||
colorArg: never
|
||||
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
|
||||
|
||||
## Using git config
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: true
|
||||
```
|
||||
|
||||
If you set `useConfig: true`, lazygit will use whatever pager is specified in $GIT_PAGER, $PAGER, or your git config. If the pager ends with something like ' | less' we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
|
||||
@@ -1,59 +0,0 @@
|
||||
## 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 |
|
||||
@@ -1,22 +1,46 @@
|
||||
# Lazygit Keybindings
|
||||
# Lazygit menu
|
||||
|
||||
## Global Keybindings
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<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
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Branches Tab)
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>S</kbd>: stash files
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>t</kbd>: add patch
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
@@ -25,68 +49,12 @@
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>r</kbd>: rebase checked-out branch onto this branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge into currently checked out branch
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>M</kbd>: merge into currently checked out branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>r</kbd>: rebase checked-out branch onto this branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit Files Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commits Panel
|
||||
|
||||
<pre>
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Commits Tab)
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash down
|
||||
@@ -107,42 +75,46 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Reflog Tab)
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Files Panel
|
||||
## Commit files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>s</kbd>: stash changes
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main Panel (Merging)
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage line
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
@@ -154,71 +126,3 @@
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll down (fn+up)
|
||||
<kbd>¦</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Main Panel (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Menu Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close menu
|
||||
<kbd>q</kbd>: close menu
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
# Lazygit Keybindings
|
||||
# Lazygit menu
|
||||
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: verversen
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Branches Tab)
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>s</kbd>: wissel naar een recente repo
|
||||
</pre>
|
||||
|
||||
## Bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>S</kbd>: stash-bestanden
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>t</kbd>: bewerkingen toevoegen
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
@@ -27,66 +51,10 @@
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit bestanden Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug
|
||||
<kbd>c</kbd>: bestand uitchecken
|
||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commits Panel
|
||||
|
||||
<pre>
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Commits Tab)
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash beneden
|
||||
@@ -107,42 +75,39 @@
|
||||
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Commits Panel (Reflog Tab)
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Bestanden Panel
|
||||
## Commit bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>esc</kbd>: ga terug
|
||||
<kbd>c</kbd>: bestand uitchecken
|
||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>s</kbd>: stash-bestanden
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Merging)
|
||||
## Hoofd (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage lijn
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
@@ -155,70 +120,9 @@
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Normaal)
|
||||
## Hoofd (Normaal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>¦</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
</pre>
|
||||
|
||||
## Menu Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close menu
|
||||
<kbd>q</kbd>: close menu
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
# Lazygit Keybindings
|
||||
# Lazygit menu
|
||||
|
||||
## Globalne
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<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>: odśwież
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Branches Tab)
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>S</kbd>: przechowaj pliki
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>t</kbd>: dodaj łatkę
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Gałęzie
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
@@ -27,66 +51,10 @@
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Remote Branches (in Remotes tab))
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to remotes list
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: przełącz
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>u</kbd>: set as upstream of checked-out branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Remotes Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: fetch remote
|
||||
<kbd>n</kbd>: add new remote
|
||||
<kbd>d</kbd>: remove remote
|
||||
<kbd>e</kbd>: edit remote
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Tags Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
<kbd>d</kbd>: delete tag
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit files Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>space</kbd>: toggle file included in patch
|
||||
<kbd>enter</kbd>: enter file to add selected lines to the patch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commity Panel
|
||||
|
||||
<pre>
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commity Panel (Commits Tab)
|
||||
## Commity
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: ściśnij w dół
|
||||
@@ -107,42 +75,46 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>i</kbd>: select commit to diff with another commit
|
||||
<kbd>T</kbd>: tag commit
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Commity Panel (Reflog Tab)
|
||||
## Schowek
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
</pre>
|
||||
|
||||
## Pliki Panel
|
||||
## Commit files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>s</kbd>: przechowaj pliki
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main Panel (Merging)
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: zatwierdź linię
|
||||
<kbd>a</kbd>: zatwierdź kawałek
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
@@ -154,71 +126,3 @@
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll down (fn+up)
|
||||
<kbd>¦</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Main Panel (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
</pre>
|
||||
|
||||
## Menu Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close menu
|
||||
<kbd>q</kbd>: close menu
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Schowek Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
@@ -1,44 +1,69 @@
|
||||
module github.com/jesseduffield/lazygit
|
||||
|
||||
go 1.14
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
||||
github.com/aws/aws-sdk-go v1.15.21 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/emirpasic/gods v1.9.0 // indirect
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/gliderlabs/ssh v0.2.2 // indirect
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-ini/ini v1.38.2 // indirect
|
||||
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.4.0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a
|
||||
github.com/jesseduffield/pty v1.2.1
|
||||
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.11 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.8
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 // indirect
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 // indirect
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc // indirect
|
||||
github.com/hashicorp/go-version v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect
|
||||
github.com/integrii/flaggy v1.2.2
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508
|
||||
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb // indirect
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 // indirect
|
||||
github.com/magiconair/properties v1.8.0 // indirect
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.2 // indirect
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/gox v1.0.1 // indirect
|
||||
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/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff // indirect
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2
|
||||
github.com/onsi/ginkgo v1.9.0 // indirect
|
||||
github.com/onsi/gomega v1.6.0 // indirect
|
||||
github.com/pelletier/go-buffruneio v0.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
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/sirupsen/logrus v1.3.0
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
|
||||
github.com/spf13/afero v1.1.1 // indirect
|
||||
github.com/spf13/cast v1.2.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect
|
||||
github.com/spf13/pflag v1.0.2 // indirect
|
||||
github.com/spf13/viper v1.1.0
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/src-d/gcfg v1.3.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/tcnksm/go-gitconfig v0.1.2
|
||||
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
|
||||
github.com/ulikunitz/xz v0.5.4 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.0 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
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.7
|
||||
gopkg.in/ini.v1 v1.46.0 // indirect
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0 // indirect
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
)
|
||||
|
||||
@@ -1,291 +1,184 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/aws/aws-sdk-go v1.15.21 h1:STLvc6RrpycslC1NRtTvt/YSgDkIGCTrB9K9vE5R2oQ=
|
||||
github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
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/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=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ini/ini v1.38.2 h1:6Hl/z3p3iFkA0dlDfzYxuFuUGD+kaweypF6btsR2/Q4=
|
||||
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.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=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 h1:MFPzqpPED05pFyGjNPJEC2sXM6EHTzFyvX+0s0JoZ48=
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU=
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc h1:wAa9fGALVHfjYxZuXRnmuJG2CnwRpJYOTvY6YdErAh0=
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/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.4.0 h1:A1x7SYx4jqu5NSrY14z8Z+0UyX2S5ygfJJrfolWR3zM=
|
||||
github.com/integrii/flaggy v1.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||
github.com/integrii/flaggy v1.2.2 h1:SzL5kyEaW+Cb3RLxGG1ch9FFDLQPB6QuMdYoNu5JIo0=
|
||||
github.com/integrii/flaggy v1.2.2/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.20200301081700-d6e485450113 h1:jHZRVJUWsU8HaQ0crocz0i0BkpOqFLDJEO/AtBp+Ecs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a h1:JSORQue6V4bMppr22dtUuYX+w79cgupo66PcGZ9ijlU=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a/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/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/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns=
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191030094731-3cf1932858a3 h1:f28aZSb9uu/yTb2rCLmhLRZTsIzz2JkktXUhwvILAn0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191030094731-3cf1932858a3/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
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 v0.0.0-20181218102224-02db52c7e406 h1:iYMH6h6SuWuBkIzRtymosE8NpSgTK0oRMfyTdVWgxzc=
|
||||
github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00/go.mod h1:cWNQljQAWYBp4wchyGfql4q2jRNZXxiE1KhVQgz+JaM=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 h1:CRD7bVjlGIiV+M0jlsa+XWpneW0KY0e7Y4z3GWb5S4o=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7/go.mod h1:VspA3aTkEo0Q7TPCLmX1uHNP+Wb4iSDX09hmTRo1QYc=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/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=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 h1:S38dC4mEwxdw/U41+97VWdbun8mTcTjwg5Ujfg8QPME=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/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=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
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.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.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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
||||
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
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.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/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff h1:jM4Eo4qMmmcqePS3u6X2lcEELtVuXWkWJIS/pRI3oSk=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/ginkgo v1.9.0 h1:SZjF721BByVj8QH636/8S2DnX4n0Re3SteMmw3N+tzc=
|
||||
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.6.0 h1:8XTW0fcJZEq9q+Upcyws4JSGua2MFysCL5xkaSgHc+M=
|
||||
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/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=
|
||||
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||
github.com/sirupsen/logrus v1.2.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/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/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/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/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/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
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/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I=
|
||||
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 h1:kJI9pPzfsULT/72wy7mxkRQZPtKWgFdCA2RTGZ4v8/E=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4=
|
||||
github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg=
|
||||
github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=
|
||||
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/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=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-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=
|
||||
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=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/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=
|
||||
gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
|
||||
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0 h1:VGbrP1EsYxtvVPEiHui+4//imr4E5MGEFLx66bQtusg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0/go.mod h1:ZHSF0JP+7oD97194otDUCD7Ofbk63+xFcfWP5bT6h+Q=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714 h1:+wM2BGgQ1znCKBexOB4OrGVSDw8mtKNUSq3wqxZhi/k=
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
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.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=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -19,6 +20,11 @@ var (
|
||||
buildSource = "unknown"
|
||||
)
|
||||
|
||||
func projectPath(path string) string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
return filepath.FromSlash(gopath + "/src/github.com/jesseduffield/lazygit/" + path)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ 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"
|
||||
)
|
||||
@@ -72,7 +73,9 @@ 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)
|
||||
@@ -82,6 +85,11 @@ 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(),
|
||||
|
||||
+44
-5
@@ -1,11 +1,50 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
Name string
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
UpstreamName string
|
||||
Name string
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, b.GetColor())
|
||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
}
|
||||
|
||||
return []string{b.Recency, displayName}
|
||||
}
|
||||
|
||||
// GetColor branch color
|
||||
func (b *Branch) GetColor() color.Attribute {
|
||||
switch b.getType() {
|
||||
case "feature":
|
||||
return color.FgGreen
|
||||
case "bugfix":
|
||||
return color.FgYellow
|
||||
case "hotfix":
|
||||
return color.FgRed
|
||||
default:
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
|
||||
// expected to return feature/bugfix/hotfix or blank string
|
||||
func (b *Branch) getType() string {
|
||||
return strings.Split(b.Name, "/")[0]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
)
|
||||
|
||||
// context:
|
||||
@@ -34,97 +36,82 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchLis
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainCurrentBranchName() string {
|
||||
func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
||||
branchName, err := b.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return branchName
|
||||
|
||||
return &Branch{Name: strings.TrimSpace(branchName)}
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainBranches() []*Branch {
|
||||
cmdStr := `git branch --format="%(refname:short)|%(upstream:short)|%(upstream:track)"`
|
||||
output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr)
|
||||
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 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}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return uniqueByName(branches)
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainSafeBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
|
||||
bIter, err := b.GitCommand.Repo.Branches()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
outputLines := strings.Split(trimmedOutput, "\n")
|
||||
if len(outputLines) <= 1 {
|
||||
return []*Branch{}
|
||||
}
|
||||
branches := make([]*Branch, len(outputLines)-1)
|
||||
for i, line := range outputLines[1:] {
|
||||
split := strings.Split(line, SEPARATION_CHAR)
|
||||
|
||||
name := split[0]
|
||||
branches[i] = &Branch{
|
||||
Name: name,
|
||||
Pullables: "?",
|
||||
Pushables: "?",
|
||||
}
|
||||
upstreamName := split[1]
|
||||
if upstreamName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
branches[i].UpstreamName = upstreamName
|
||||
|
||||
track := split[2]
|
||||
re := regexp.MustCompile(`ahead (\d+)`)
|
||||
match := re.FindStringSubmatch(track)
|
||||
if len(match) > 1 {
|
||||
branches[i].Pushables = match[1]
|
||||
} else {
|
||||
branches[i].Pushables = "0"
|
||||
}
|
||||
|
||||
re = regexp.MustCompile(`behind (\d+)`)
|
||||
match = re.FindStringSubmatch(track)
|
||||
if len(match) > 1 {
|
||||
branches[i].Pullables = match[1]
|
||||
} else {
|
||||
branches[i].Pullables = "0"
|
||||
}
|
||||
}
|
||||
bIter.ForEach(func(b *plumbing.Reference) error {
|
||||
name := b.Name().Short()
|
||||
branches = append(branches, &Branch{Name: name})
|
||||
return nil
|
||||
})
|
||||
|
||||
return branches
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*Branch, included bool) []*Branch {
|
||||
for _, newBranch := range newBranches {
|
||||
if included == branchIncluded(newBranch.Name, existingBranches) {
|
||||
finalBranches = append(finalBranches, newBranch)
|
||||
}
|
||||
}
|
||||
return finalBranches
|
||||
}
|
||||
|
||||
func sanitisedReflogName(reflogBranch *Branch, safeBranches []*Branch) string {
|
||||
for _, safeBranch := range safeBranches {
|
||||
if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) {
|
||||
return safeBranch.Name
|
||||
}
|
||||
}
|
||||
return reflogBranch.Name
|
||||
}
|
||||
|
||||
// Build the list of branches for the current repo
|
||||
func (b *BranchListBuilder) Build() []*Branch {
|
||||
currentBranchName := b.obtainCurrentBranchName()
|
||||
branches := b.obtainBranches()
|
||||
branches := make([]*Branch, 0)
|
||||
head := b.obtainCurrentBranch()
|
||||
safeBranches := b.obtainSafeBranches()
|
||||
|
||||
reflogBranches := b.obtainReflogBranches()
|
||||
|
||||
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
|
||||
branchesWithRecency := make([]*Branch, 0)
|
||||
outer:
|
||||
for _, reflogBranch := range reflogBranches {
|
||||
for j, branch := range branches {
|
||||
if strings.EqualFold(reflogBranch.Name, branch.Name) {
|
||||
branch.Recency = reflogBranch.Recency
|
||||
branchesWithRecency = append(branchesWithRecency, branch)
|
||||
branches = append(branches[0:j], branches[j+1:]...)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
for i, reflogBranch := range reflogBranches {
|
||||
reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches)
|
||||
}
|
||||
|
||||
branches = append(branchesWithRecency, branches...)
|
||||
branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true)
|
||||
branches = b.appendNewBranches(branches, safeBranches, branches, false)
|
||||
|
||||
// if it's the current branch we need to pull it up to the top
|
||||
for i, branch := range branches {
|
||||
if branch.Name == currentBranchName {
|
||||
branches = append(branches[0:i], branches[i+1:]...)
|
||||
branches = append([]*Branch{branch}, branches...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(branches) == 0 || branches[0].Name != currentBranchName {
|
||||
branches = append([]*Branch{{Name: currentBranchName}}, branches...)
|
||||
if len(branches) == 0 || branches[0].Name != head.Name {
|
||||
branches = append([]*Branch{head}, branches...)
|
||||
}
|
||||
|
||||
branches[0].Recency = " *"
|
||||
@@ -132,19 +119,33 @@ outer:
|
||||
return branches
|
||||
}
|
||||
|
||||
func branchIncluded(branchName string, branches []*Branch) bool {
|
||||
for _, existingBranch := range branches {
|
||||
if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func uniqueByName(branches []*Branch) []*Branch {
|
||||
finalBranches := make([]*Branch, 0)
|
||||
for _, branch := range branches {
|
||||
if branchIncluded(branch.Name, finalBranches) {
|
||||
continue
|
||||
}
|
||||
finalBranches = append(finalBranches, branch)
|
||||
}
|
||||
return finalBranches
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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 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 abbreviatedTimeUnit(timeUnit string) string {
|
||||
@@ -161,30 +162,3 @@ func abbreviatedTimeUnit(timeUnit string) string {
|
||||
}
|
||||
return timeUnitMap[timeUnit]
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
// 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
|
||||
}
|
||||
|
||||
branchNameMap := map[string]bool{}
|
||||
|
||||
branchLines := utils.SplitLines(rawString)
|
||||
for _, line := range branchLines {
|
||||
recency, branchName := branchInfoFromLine(line)
|
||||
if branchName == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := branchNameMap[branchName]; ok {
|
||||
continue
|
||||
}
|
||||
branchNameMap[branchName] = true
|
||||
branch := &Branch{Name: branchName, Recency: recency}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return branches
|
||||
}
|
||||
|
||||
+49
-4
@@ -1,5 +1,11 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
@@ -8,8 +14,47 @@ 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
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
Author string
|
||||
Date string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha), actionString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// CommitFile : A git commit file
|
||||
type CommitFile struct {
|
||||
Sha string
|
||||
@@ -17,3 +22,21 @@ const (
|
||||
// PART is for when you're only talking about specific lines that have been modified
|
||||
PART
|
||||
)
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (f *CommitFile) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
|
||||
var colour *color.Color
|
||||
switch f.Status {
|
||||
case UNSELECTED:
|
||||
colour = defaultColor
|
||||
case WHOLE:
|
||||
colour = green
|
||||
case PART:
|
||||
colour = yellow
|
||||
}
|
||||
return []string{colour.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
// if we find out we need to use one of these functions in the git.go file, we
|
||||
// can just pull them out of here and put them there and then call them from in here
|
||||
|
||||
const SEPARATION_CHAR = "|"
|
||||
|
||||
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||
type CommitListBuilder struct {
|
||||
Log *logrus.Entry
|
||||
@@ -47,41 +45,8 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
|
||||
// then puts them into a commit object
|
||||
// example input:
|
||||
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||
func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
split := strings.Split(line, SEPARATION_CHAR)
|
||||
|
||||
sha := split[0]
|
||||
date := split[1]
|
||||
author := split[2]
|
||||
extraInfo := strings.TrimSpace(split[3])
|
||||
message := strings.Join(split[4:], SEPARATION_CHAR)
|
||||
tags := []string{}
|
||||
|
||||
if extraInfo != "" {
|
||||
re := regexp.MustCompile(`tag: ([^,\)]+)`)
|
||||
tagMatch := re.FindStringSubmatch(extraInfo)
|
||||
if len(tagMatch) > 1 {
|
||||
tags = append(tags, tagMatch[1])
|
||||
}
|
||||
}
|
||||
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
Name: message,
|
||||
DisplayString: line,
|
||||
Tags: tags,
|
||||
ExtraInfo: extraInfo,
|
||||
Date: date,
|
||||
Author: author,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
var rebasingCommits []*Commit
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
@@ -100,14 +65,20 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits()
|
||||
log := c.getLog(limit)
|
||||
log := c.getLog()
|
||||
|
||||
// now we can split it up and turn it into commits
|
||||
for _, line := range utils.SplitLines(log) {
|
||||
commit := c.extractCommitFromLine(line)
|
||||
_, unpushed := unpushedCommits[commit.Sha[:8]]
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, commit)
|
||||
splitLine := strings.Split(line, " ")
|
||||
sha := splitLine[0]
|
||||
_, unpushed := unpushedCommits[sha]
|
||||
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, &Commit{
|
||||
Sha: sha,
|
||||
Name: strings.Join(splitLine[1:], " "),
|
||||
Status: status,
|
||||
DisplayString: strings.Join(splitLine, " "),
|
||||
})
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
currentCommit := commits[len(rebasingCommits)]
|
||||
@@ -218,7 +189,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
|
||||
}
|
||||
splitLine := strings.Split(line, " ")
|
||||
commits = append([]*Commit{{
|
||||
Sha: splitLine[1],
|
||||
Sha: splitLine[1][0:7],
|
||||
Name: strings.Join(splitLine[2:], " "),
|
||||
Status: "rebasing",
|
||||
Action: splitLine[0],
|
||||
@@ -235,7 +206,7 @@ func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
|
||||
// Subject: second commit on master
|
||||
func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
|
||||
lines := strings.Split(content, "\n")
|
||||
sha := strings.Split(lines[0], " ")[1]
|
||||
sha := strings.Split(lines[0], " ")[1][0:7]
|
||||
name := strings.TrimPrefix(lines[3], "Subject: ")
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
@@ -290,7 +261,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("git merge-base HEAD %s", baseBranch)
|
||||
output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -298,7 +269,7 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
// to the remote branch of the current branch, a map is returned to ease look up
|
||||
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
pushables := map[string]bool{}
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit --abbrev=8")
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit")
|
||||
if err != nil {
|
||||
return pushables
|
||||
}
|
||||
@@ -309,15 +280,12 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
return pushables
|
||||
}
|
||||
|
||||
// getLog gets the git log.
|
||||
func (c *CommitListBuilder) getLog(limit bool) string {
|
||||
limitFlag := ""
|
||||
if limit {
|
||||
limitFlag = "-30"
|
||||
}
|
||||
|
||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%ar%s%%aN%s%%d%s%%s\" %s --abbrev=%d", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20))
|
||||
|
||||
// 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")
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
|
||||
@@ -149,3 +149,166 @@ func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetLog is a function.
|
||||
func TestCommitListBuilderGetLog(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Retrieves logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
|
||||
return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line")
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"An error occurred when retrieving logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(output string) {
|
||||
assert.Empty(t, output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getLog())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetCommits is a function.
|
||||
func TestCommitListBuilderGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func([]*Commit, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"No data found",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("test")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits returns 2 commits, 1 unpushed, the other merged",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 2)
|
||||
assert.EqualValues(t, []*Commit{
|
||||
{
|
||||
Sha: "8a2bb0e",
|
||||
Name: "commit 1",
|
||||
Status: "unpushed",
|
||||
DisplayString: "8a2bb0e commit 1",
|
||||
},
|
||||
{
|
||||
Sha: "78976bc",
|
||||
Name: "commit 2",
|
||||
Status: "merged",
|
||||
DisplayString: "78976bc commit 2",
|
||||
},
|
||||
}, commits)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits bubbles up an error from setCommitMergedStatuses",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
// here's where we are returning the error
|
||||
return exec.Command("test")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
// here too
|
||||
return exec.Command("test")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.GetCommits())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package commands
|
||||
|
||||
import "github.com/fatih/color"
|
||||
|
||||
// File : A file from git status
|
||||
// duplicating this for now
|
||||
type File struct {
|
||||
@@ -14,3 +16,23 @@ type File struct {
|
||||
Type string // one of 'file', 'directory', and 'other'
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a file
|
||||
func (f *File) GetDisplayStrings(isFocused bool) []string {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !f.Tracked && !f.HasStagedChanges {
|
||||
return []string{red.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
output := green.Sprint(f.DisplayString[0:1])
|
||||
output += red.Sprint(f.DisplayString[1:3])
|
||||
if f.HasUnstagedChanges {
|
||||
output += red.Sprint(f.Name)
|
||||
} else {
|
||||
output += green.Sprint(f.Name)
|
||||
}
|
||||
return []string{output}
|
||||
}
|
||||
|
||||
+108
-228
@@ -6,8 +6,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -23,13 +21,7 @@ import (
|
||||
gogit "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
func verifyInGitRepo(runCmd func(string) error) error {
|
||||
return runCmd("git status")
|
||||
}
|
||||
|
||||
@@ -158,9 +150,7 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
|
||||
|
||||
// GetStashEntries stash entries
|
||||
func (c *GitCommand) GetStashEntries() []*StashEntry {
|
||||
// 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)
|
||||
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
|
||||
stashEntries := []*StashEntry{}
|
||||
for i, line := range utils.SplitLines(rawString) {
|
||||
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
|
||||
@@ -177,8 +167,8 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
||||
}
|
||||
|
||||
// GetStashEntryDiff stash diff
|
||||
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||
return fmt.Sprintf("git stash show -p --color=%s stash@{%d}", c.colorArg(), index)
|
||||
func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git stash show -p --color stash@{" + fmt.Sprint(index) + "}")
|
||||
}
|
||||
|
||||
// GetStatusFiles git status files
|
||||
@@ -192,10 +182,10 @@ func (c *GitCommand) GetStatusFiles() []*File {
|
||||
stagedChange := change[0:1]
|
||||
unstagedChange := statusString[1:2]
|
||||
filename := c.OSCommand.Unquote(statusString[3:])
|
||||
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)
|
||||
_, 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"
|
||||
|
||||
file := &File{
|
||||
Name: filename,
|
||||
@@ -216,13 +206,13 @@ func (c *GitCommand) GetStatusFiles() []*File {
|
||||
|
||||
// StashDo modify stash
|
||||
func (c *GitCommand) StashDo(index int, method string) error {
|
||||
return c.OSCommand.RunCommand("git stash %s stash@{%d}", method, index)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash %s stash@{%d}", method, index))
|
||||
}
|
||||
|
||||
// StashSave save stash
|
||||
// TODO: before calling this, check if there is anything to save
|
||||
func (c *GitCommand) StashSave(message string) error {
|
||||
return c.OSCommand.RunCommand("git stash save %s", c.OSCommand.Quote(message))
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git stash save %s", c.OSCommand.Quote(message)))
|
||||
}
|
||||
|
||||
// MergeStatusFiles merge status files
|
||||
@@ -266,7 +256,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.ResetHard("HEAD"); err != nil {
|
||||
if err := c.ResetHardHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -274,22 +264,23 @@ func (c *GitCommand) ResetAndClean() error {
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
||||
return c.GetCommitDifferences("HEAD", "HEAD@{u}")
|
||||
return c.GetCommitDifferences("HEAD", "@{u}")
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
|
||||
return c.GetCommitDifferences(branchName, branchName+"@{u}")
|
||||
upstream := "origin" // hardcoded for now
|
||||
return c.GetCommitDifferences(branchName, fmt.Sprintf("%s/%s", upstream, branchName))
|
||||
}
|
||||
|
||||
// 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(command, to, from)
|
||||
pushableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, to, from))
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to)
|
||||
pullableCount, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(command, from, to))
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
@@ -298,7 +289,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("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name))
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name)))
|
||||
}
|
||||
|
||||
// RebaseBranch interactive rebases onto a branch
|
||||
@@ -323,27 +314,24 @@ 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("git reset --%s %s", strength, sha)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git reset --%s %s", strength, sha))
|
||||
}
|
||||
|
||||
// NewBranch create new branch
|
||||
func (c *GitCommand) NewBranch(name string, baseBranch string) error {
|
||||
return c.OSCommand.RunCommand("git checkout -b %s %s", name, baseBranch)
|
||||
func (c *GitCommand) NewBranch(name string) error {
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("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 == "HEAD\n" {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
||||
if err != nil {
|
||||
branchName, err = c.OSCommand.RunCommandWithOutput("git rev-parse --short HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||
match := re.FindStringSubmatch(output)
|
||||
branchName = match[1]
|
||||
}
|
||||
return strings.TrimSpace(branchName), nil
|
||||
return utils.TrimTrailingNewline(branchName), nil
|
||||
}
|
||||
|
||||
// DeleteBranch delete branch
|
||||
@@ -354,7 +342,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error {
|
||||
command = "git branch -D"
|
||||
}
|
||||
|
||||
return c.OSCommand.RunCommand("%s %s", command, branch)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("%s %s", command, branch))
|
||||
}
|
||||
|
||||
// ListStash list stash
|
||||
@@ -364,7 +352,7 @@ func (c *GitCommand) ListStash() (string, error) {
|
||||
|
||||
// Merge merge
|
||||
func (c *GitCommand) Merge(branchName string) error {
|
||||
return c.OSCommand.RunCommand("git merge --no-edit %s", branchName)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git merge --no-edit %s", branchName))
|
||||
}
|
||||
|
||||
// AbortMerge abort merge
|
||||
@@ -405,39 +393,29 @@ func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
// Pull pulls from repo
|
||||
func (c *GitCommand) Pull(args string, ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit "+args, ask)
|
||||
}
|
||||
|
||||
// PullWithoutPasswordCheck assumes that the pull will not prompt the user for a password
|
||||
func (c *GitCommand) PullWithoutPasswordCheck(args string) error {
|
||||
return c.OSCommand.RunCommand("git pull --no-edit " + args)
|
||||
func (c *GitCommand) Pull(ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit", ask)
|
||||
}
|
||||
|
||||
// Push pushes to a branch
|
||||
func (c *GitCommand) Push(branchName string, force bool, upstream string, args string, ask func(string) string) error {
|
||||
func (c *GitCommand) Push(branchName string, force bool, ask func(string) string) error {
|
||||
forceFlag := ""
|
||||
if force {
|
||||
forceFlag = "--force-with-lease"
|
||||
forceFlag = "--force-with-lease "
|
||||
}
|
||||
|
||||
setUpstreamArg := ""
|
||||
if upstream != "" {
|
||||
setUpstreamArg = "--set-upstream " + upstream
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("git push --follow-tags %s %s %s", forceFlag, setUpstreamArg, args)
|
||||
cmd := fmt.Sprintf("git push %s-u origin %s", forceFlag, branchName)
|
||||
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("cat %s", c.OSCommand.Quote(fileName))
|
||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName)))
|
||||
}
|
||||
|
||||
// StageFile stages a file
|
||||
func (c *GitCommand) StageFile(fileName string) error {
|
||||
return c.OSCommand.RunCommand("git add %s", c.OSCommand.Quote(fileName))
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git add %s", c.OSCommand.Quote(fileName)))
|
||||
}
|
||||
|
||||
// StageAll stages all files
|
||||
@@ -460,7 +438,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(command, c.OSCommand.Quote(name)); err != nil {
|
||||
if err := c.OSCommand.RunCommand(fmt.Sprintf(command, c.OSCommand.Quote(name))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -504,7 +482,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("git reset -- %s", quotedFileName); err != nil {
|
||||
if err := c.OSCommand.RunCommand(fmt.Sprintf("git reset -- %s", quotedFileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -518,16 +496,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("git checkout -- %s", quotedFileName)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -- %s", quotedFileName))
|
||||
}
|
||||
|
||||
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
||||
// Checkout checks out a branch, 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("git checkout %s %s", forceArg, branch)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git checkout %s %s", forceArg, branch))
|
||||
}
|
||||
|
||||
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
||||
@@ -544,13 +522,7 @@ 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) {
|
||||
cmdStr := c.GetBranchGraphCmdStr(branchName)
|
||||
return c.OSCommand.RunCommandWithOutput(cmdStr)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", branchName)
|
||||
return strings.TrimSpace(output), err
|
||||
return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName))
|
||||
}
|
||||
|
||||
// Ignore adds a file to the gitignore for the repo
|
||||
@@ -558,12 +530,41 @@ func (c *GitCommand) Ignore(filename string) error {
|
||||
return c.OSCommand.AppendLineToFile(".gitignore", filename)
|
||||
}
|
||||
|
||||
func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s", c.colorArg(), sha)
|
||||
}
|
||||
// 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) GetBranchGraphCmdStr(branchName string) string {
|
||||
return fmt.Sprintf("git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium %s --", branchName)
|
||||
// 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
|
||||
}
|
||||
|
||||
// GetRemoteURL returns current repo remote url
|
||||
@@ -574,43 +575,41 @@ func (c *GitCommand) GetRemoteURL() string {
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
func (c *GitCommand) CheckRemoteBranchExists(branch *Branch) bool {
|
||||
_, err := c.OSCommand.RunCommandWithOutput(
|
||||
_, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf(
|
||||
"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 := c.colorArg()
|
||||
colorArg := "--color"
|
||||
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
||||
fileName := c.OSCommand.Quote(split[len(split)-1])
|
||||
if cached {
|
||||
cachedArg = "--cached"
|
||||
}
|
||||
if !file.Tracked && !file.HasStagedChanges && !cached {
|
||||
if !file.Tracked && !file.HasStagedChanges {
|
||||
trackedArg = "--no-index /dev/null"
|
||||
}
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
colorArg = ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("git diff --color=%s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||
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
|
||||
}
|
||||
|
||||
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||
c.Log.Warn(patch)
|
||||
filepath := filepath.Join(c.Config.GetUserConfigDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
|
||||
filepath := filepath.Join(c.Config.GetUserConfigDir(), utils.GetCurrentRepoName(), time.Now().Format(time.StampNano)+".patch")
|
||||
if err := c.OSCommand.CreateFileWithContent(filepath, patch); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -620,21 +619,20 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||
flagStr += " --" + flag
|
||||
}
|
||||
|
||||
return c.OSCommand.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)))
|
||||
}
|
||||
|
||||
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) 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) RunSkipEditorCommand(command string) error {
|
||||
cmd := c.OSCommand.ExecutableFromString(command)
|
||||
lazyGitPath := c.OSCommand.GetLazygitPath()
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
|
||||
"EDITOR="+lazyGitPath,
|
||||
"VISUAL="+lazyGitPath,
|
||||
"EDITOR="+c.OSCommand.GetLazygitPath(),
|
||||
)
|
||||
return c.OSCommand.RunExecutable(cmd)
|
||||
}
|
||||
@@ -846,7 +844,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("git revert %s", sha)
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git revert %s", sha))
|
||||
}
|
||||
|
||||
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
||||
@@ -866,7 +864,8 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
|
||||
|
||||
// GetCommitFiles get the specified commit files
|
||||
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
||||
files, err := c.OSCommand.RunCommandWithOutput("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||
cmd := fmt.Sprintf("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||
files, err := c.OSCommand.RunCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -892,22 +891,18 @@ 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 := c.colorArg()
|
||||
colorArg := "--color"
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
colorArg = ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("git show --no-renames --color=%s %s -- %s", colorArg, commitSha, fileName)
|
||||
cmd := fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
||||
return c.OSCommand.RunCommandWithOutput(cmd)
|
||||
}
|
||||
|
||||
// CheckoutFile checks out the file for the given commit
|
||||
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
|
||||
return c.OSCommand.RunCommand("git checkout %s %s", commitSha, fileName)
|
||||
cmd := fmt.Sprintf("git checkout %s %s", commitSha, fileName)
|
||||
return c.OSCommand.RunCommand(cmd)
|
||||
}
|
||||
|
||||
// DiscardOldFileChanges discards changes to a file from an old commit
|
||||
@@ -917,7 +912,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("git cat-file -e HEAD^:%s", fileName); err != nil {
|
||||
if err := c.OSCommand.RunCommand(fmt.Sprintf("git cat-file -e HEAD^:%s", fileName)); err != nil {
|
||||
if err := c.OSCommand.Remove(fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -946,34 +941,31 @@ func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
|
||||
return c.OSCommand.RunCommand("git checkout -- .")
|
||||
}
|
||||
|
||||
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
|
||||
func (c *GitCommand) RemoveTrackedFiles(name string) error {
|
||||
return c.OSCommand.RunCommand("git rm -r --cached %s", name)
|
||||
}
|
||||
|
||||
// RemoveUntrackedFiles runs `git clean -fd`
|
||||
func (c *GitCommand) RemoveUntrackedFiles() error {
|
||||
return c.OSCommand.RunCommand("git clean -fd")
|
||||
}
|
||||
|
||||
// ResetHardHead runs `git reset --hard`
|
||||
func (c *GitCommand) ResetHard(ref string) error {
|
||||
return c.OSCommand.RunCommand("git reset --hard " + ref)
|
||||
// ResetHardHead runs `git reset --hard HEAD`
|
||||
func (c *GitCommand) ResetHardHead() error {
|
||||
return c.OSCommand.RunCommand("git reset --hard HEAD")
|
||||
}
|
||||
|
||||
// ResetSoft runs `git reset --soft HEAD`
|
||||
func (c *GitCommand) ResetSoft(ref string) error {
|
||||
return c.OSCommand.RunCommand("git reset --soft " + ref)
|
||||
// ResetSoftHead runs `git reset --soft HEAD`
|
||||
func (c *GitCommand) ResetSoftHead() error {
|
||||
return c.OSCommand.RunCommand("git reset --soft HEAD")
|
||||
}
|
||||
|
||||
// DiffCommits show diff between commits
|
||||
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git diff --color=%s --stat -p %s %s", c.colorArg(), sha1, sha2)
|
||||
cmd := fmt.Sprintf("git diff --color %s %s", sha1, sha2)
|
||||
return c.OSCommand.RunCommandWithOutput(cmd)
|
||||
}
|
||||
|
||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||
func (c *GitCommand) CreateFixupCommit(sha string) error {
|
||||
return c.OSCommand.RunCommand("git commit --fixup=%s", sha)
|
||||
cmd := fmt.Sprintf("git commit --fixup=%s", sha)
|
||||
return c.OSCommand.RunCommand(cmd)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -1055,115 +1047,3 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*Commit, commitIn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
|
||||
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 --abbrev=20")
|
||||
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
|
||||
}
|
||||
|
||||
func (c *GitCommand) ConfiguredPager() string {
|
||||
if os.Getenv("GIT_PAGER") != "" {
|
||||
return os.Getenv("GIT_PAGER")
|
||||
}
|
||||
if os.Getenv("PAGER") != "" {
|
||||
return os.Getenv("PAGER")
|
||||
}
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git config --get-all core.pager")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
return strings.Split(trimmedOutput, "\n")[0]
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetPager(width int) string {
|
||||
useConfig := c.Config.GetUserConfig().GetBool("git.paging.useConfig")
|
||||
if useConfig {
|
||||
pager := c.ConfiguredPager()
|
||||
return strings.Split(pager, "| less")[0]
|
||||
}
|
||||
|
||||
templateValues := map[string]string{
|
||||
"columnWidth": strconv.Itoa(width/2 - 6),
|
||||
}
|
||||
|
||||
pagerTemplate := c.Config.GetUserConfig().GetString("git.paging.pager")
|
||||
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
|
||||
}
|
||||
|
||||
func (c *GitCommand) colorArg() string {
|
||||
return c.Config.GetUserConfig().GetString("git.paging.colorArg")
|
||||
}
|
||||
|
||||
func (c *GitCommand) RenameBranch(oldName string, newName string) error {
|
||||
return c.OSCommand.RunCommand("git branch --move %s %s", oldName, newName)
|
||||
}
|
||||
|
||||
+97
-56
@@ -5,7 +5,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -59,14 +58,14 @@ func (f fileInfoMock) Sys() interface{} {
|
||||
func TestVerifyInGitRepo(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runCmd func(string, ...interface{}) error
|
||||
runCmd func(string) error
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Valid git repository",
|
||||
func(string, ...interface{}) error {
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
@@ -75,7 +74,7 @@ func TestVerifyInGitRepo(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"Not a valid git repository",
|
||||
func(string, ...interface{}) error {
|
||||
func(string) error {
|
||||
return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
|
||||
},
|
||||
func(err error) {
|
||||
@@ -319,6 +318,21 @@ 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 {
|
||||
@@ -636,12 +650,12 @@ func TestGitCommandNewBranch(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args)
|
||||
assert.EqualValues(t, []string{"checkout", "-b", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
|
||||
assert.NoError(t, gitCmd.NewBranch("test", "master"))
|
||||
assert.NoError(t, gitCmd.NewBranch("test"))
|
||||
}
|
||||
|
||||
// TestGitCommandDeleteBranch is a function.
|
||||
@@ -976,7 +990,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", "--follow-tags"}, args)
|
||||
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -989,7 +1003,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", "--follow-tags", "--force-with-lease"}, args)
|
||||
assert.EqualValues(t, []string{"push", "--force-with-lease", "-u", "origin", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1002,7 +1016,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", "--follow-tags"}, args)
|
||||
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
|
||||
return exec.Command("test")
|
||||
},
|
||||
false,
|
||||
@@ -1016,7 +1030,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)
|
||||
@@ -1397,6 +1411,66 @@ 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 {
|
||||
@@ -1449,7 +1523,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=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "-100", "test"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
@@ -1473,7 +1547,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"Default case",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1489,7 +1563,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"cached",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--cached", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1505,7 +1579,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"plain",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color=never", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1521,7 +1595,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"File not tracked and file has no staged changes",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--no-index", "/dev/null", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color", "--no-index", "/dev/null", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1565,7 +1639,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"falls back to git `git branch --contains` if symbolic-ref fails",
|
||||
"falls back to git rev-parse if symbolic-ref fails",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
@@ -1573,9 +1647,9 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("test")
|
||||
case "branch":
|
||||
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
||||
return exec.Command("echo", "* master")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1871,7 +1945,7 @@ func TestGitCommandShowCommitFile(t *testing.T) {
|
||||
"hello.txt",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: "git show --no-renames --color=never 123456 -- hello.txt",
|
||||
Expect: "git show --no-renames 123456 -- hello.txt",
|
||||
Replace: "echo -n hello",
|
||||
},
|
||||
}),
|
||||
@@ -2032,11 +2106,10 @@ func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandResetHard is a function.
|
||||
func TestGitCommandResetHard(t *testing.T) {
|
||||
// TestGitCommandResetHardHead is a function.
|
||||
func TestGitCommandResetHardHead(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
ref string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
@@ -2044,7 +2117,6 @@ func TestGitCommandResetHard(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
"HEAD",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git reset --hard HEAD`,
|
||||
@@ -2062,7 +2134,7 @@ func TestGitCommandResetHard(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCmd.OSCommand.command = s.command
|
||||
s.test(gitCmd.ResetHard(s.ref))
|
||||
s.test(gitCmd.ResetHardHead())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2102,37 +2174,6 @@ 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
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
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
|
||||
}
|
||||
+4
-30
@@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -36,7 +35,6 @@ 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
|
||||
}
|
||||
@@ -48,7 +46,6 @@ 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,
|
||||
}
|
||||
@@ -60,21 +57,8 @@ 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
|
||||
// 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...)
|
||||
}
|
||||
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
cmd := c.ExecutableFromString(command)
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
@@ -82,7 +66,6 @@ func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...inte
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
@@ -131,8 +114,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(formatString string, formatArgs ...interface{}) error {
|
||||
_, err := c.RunCommandWithOutput(formatString, formatArgs...)
|
||||
func (c *OSCommand) RunCommand(command string) error {
|
||||
_, err := c.RunCommandWithOutput(command)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -315,7 +298,6 @@ 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)
|
||||
@@ -334,7 +316,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
|
||||
@@ -401,11 +383,3 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func coloredString(colorAttr color.Attribute, str string, selected bool, include
|
||||
var cl *color.Color
|
||||
attributes := []color.Attribute{colorAttr}
|
||||
if selected {
|
||||
attributes = append(attributes, theme.SelectedLineBgColor)
|
||||
attributes = append(attributes, color.BgBlue)
|
||||
}
|
||||
cl = color.New(attributes...)
|
||||
var clIncluded *color.Color
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
)
|
||||
|
||||
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||
@@ -27,63 +26,27 @@ type RepoInformation struct {
|
||||
Repository string
|
||||
}
|
||||
|
||||
// NewService builds a Service based on the host type
|
||||
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
|
||||
var service *Service
|
||||
|
||||
switch typeName {
|
||||
case "github":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/compare/%s?expand=1"),
|
||||
}
|
||||
case "bitbucket":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/pull-requests/new?source=%s&t=1"),
|
||||
}
|
||||
case "gitlab":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/merge_requests/new?merge_request[source_branch]=%s"),
|
||||
}
|
||||
func getServices() []*Service {
|
||||
return []*Service{
|
||||
{
|
||||
Name: "github.com",
|
||||
PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
|
||||
},
|
||||
{
|
||||
Name: "bitbucket.org",
|
||||
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
|
||||
},
|
||||
{
|
||||
Name: "gitlab.com",
|
||||
PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
|
||||
},
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func getServices(config config.AppConfigurer) []*Service {
|
||||
services := []*Service{
|
||||
NewService("github", "github.com", "github.com"),
|
||||
NewService("bitbucket", "bitbucket.org", "bitbucket.org"),
|
||||
NewService("gitlab", "gitlab.com", "gitlab.com"),
|
||||
}
|
||||
|
||||
configServices := config.GetUserConfig().GetStringMapString("services")
|
||||
|
||||
for repoDomain, typeAndDomain := range configServices {
|
||||
splitData := strings.Split(typeAndDomain, ":")
|
||||
if len(splitData) != 2 {
|
||||
// TODO log this misconfiguration
|
||||
continue
|
||||
}
|
||||
|
||||
service := NewService(splitData[0], repoDomain, splitData[1])
|
||||
if service == nil {
|
||||
// TODO log this unsupported service
|
||||
continue
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
// NewPullRequest creates new instance of PullRequest
|
||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||
return &PullRequest{
|
||||
GitServices: getServices(gitCommand.Config),
|
||||
GitServices: getServices(),
|
||||
GitCommand: gitCommand,
|
||||
}
|
||||
}
|
||||
@@ -122,7 +85,7 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
if isHTTP {
|
||||
splits := strings.Split(url, "/")
|
||||
owner := strings.Join(splits[3:len(splits)-1], "/")
|
||||
owner := splits[len(splits)-2]
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
@@ -133,8 +96,8 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
tmpSplit := strings.Split(url, ":")
|
||||
splits := strings.Split(tmpSplit[1], "/")
|
||||
owner := strings.Join(splits[0:len(splits)-1], "/")
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
owner := splits[0]
|
||||
repo := strings.TrimSuffix(splits[1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
Owner: owner,
|
||||
|
||||
@@ -147,13 +147,6 @@ func TestCreatePullRequest(t *testing.T) {
|
||||
gitCommand := NewDummyGitCommand()
|
||||
gitCommand.OSCommand.command = s.command
|
||||
gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
|
||||
gitCommand.Config.GetUserConfig().Set("services", map[string]string{
|
||||
// valid configuration for a custom service URL
|
||||
"git.work.com": "gitlab:code.work.com",
|
||||
// invalid configurations for a custom service URL
|
||||
"invalid.work.com": "noservice:invalid.work.com",
|
||||
"noservice.work.com": "noservice.work.com",
|
||||
})
|
||||
dummyPullRequest := NewPullRequest(gitCommand)
|
||||
s.test(dummyPullRequest.Create(s.branch))
|
||||
})
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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)}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package commands
|
||||
|
||||
// Remote Branch : A git remote branch
|
||||
type RemoteBranch struct {
|
||||
Name string
|
||||
RemoteName string
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package commands
|
||||
|
||||
// Tag : A git tag
|
||||
type Tag struct {
|
||||
Name string
|
||||
}
|
||||
+1
-106
@@ -243,25 +243,18 @@ func GetDefaultConfig() []byte {
|
||||
scrollHeight: 2
|
||||
scrollPastBottom: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
sidePanelWidth: 0.3333
|
||||
theme:
|
||||
lightTheme: false
|
||||
activeBorderColor:
|
||||
- green
|
||||
- white
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- white
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
manualCommit: false
|
||||
skipHookPrefix: 'WIP'
|
||||
@@ -272,104 +265,6 @@ 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'
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
startSearch: '/'
|
||||
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: ':'
|
||||
createRebaseOptionsMenu: 'm'
|
||||
pushFiles: 'P'
|
||||
pullFiles: 'p'
|
||||
refresh: 'R'
|
||||
createPatchOptionsMenu: '<c-p>'
|
||||
nextTab: ']'
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
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'
|
||||
renameBranch: '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,8 +1,6 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -51,24 +49,15 @@ 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.statusManager.addWaitingStatus(name)
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
gui.statusManager.addWaitingStatus(name)
|
||||
return nil
|
||||
})
|
||||
|
||||
defer func() {
|
||||
defer gui.g.Update(func(g *gocui.Gui) error {
|
||||
gui.statusManager.removeStatus(name)
|
||||
}()
|
||||
|
||||
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
|
||||
}
|
||||
gui.renderString(gui.g, "appStatus", appStatus)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
|
||||
+104
-231
@@ -6,8 +6,6 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -21,6 +19,14 @@ 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() {
|
||||
@@ -37,31 +43,41 @@ 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.newStringTask("main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
v.FocusPoint(0, gui.State.Panels.Branches.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
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() {
|
||||
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", graph)
|
||||
}()
|
||||
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
|
||||
}
|
||||
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
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 {
|
||||
@@ -69,11 +85,9 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
}
|
||||
gui.State.Branches = builder.Build()
|
||||
|
||||
// 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" {
|
||||
if err := gui.renderLocalBranchesWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshStatus(g)
|
||||
@@ -81,19 +95,32 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderLocalBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
if gui.g.CurrentView() == branchesView {
|
||||
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
}
|
||||
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
panelState := gui.State.Panels.Branches
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
@@ -106,7 +133,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.handleCheckoutRef(branch.Name)
|
||||
return gui.handleCheckoutBranch(branch.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -137,23 +164,23 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||
title := gui.Tr.SLocalize("ForceCheckoutBranch")
|
||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Checkout(branch.Name, true); err != nil {
|
||||
_ = gui.createErrorPanel(g, err.Error())
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutRef(ref string) error {
|
||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
||||
func (gui *Gui) handleCheckoutBranch(branchName string) error {
|
||||
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
|
||||
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
|
||||
|
||||
if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
|
||||
// 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") + ref); err != nil {
|
||||
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + branchName); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
||||
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
@@ -176,56 +203,48 @@ func (gui *Gui) handleCheckoutRef(ref 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 {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutRef(gui.trimmedContent(v))
|
||||
gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutBranch(gui.trimmedContent(v))
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||
if len(gui.State.Branches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Branches[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
branch := gui.State.Branches[0]
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"NewBranchNameBranchOff",
|
||||
Teml{
|
||||
"branchName": branch.Name,
|
||||
},
|
||||
)
|
||||
return gui.createPromptPanel(g, v, message, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.NewBranch(gui.trimmedContent(v), branch.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshSidePanels(g); err != nil {
|
||||
gui.createPromptPanel(g, v, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshSidePanels(g)
|
||||
return gui.handleBranchSelect(g, v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.deleteBranch(g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.deleteBranch(g, v, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
selectedBranch := gui.getSelectedBranch()
|
||||
if selectedBranch == nil {
|
||||
return nil
|
||||
}
|
||||
checkedOutBranch := gui.getCheckedOutBranch()
|
||||
checkedOutBranch := gui.State.Branches[0]
|
||||
if checkedOutBranch.Name == selectedBranch.Name {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
|
||||
}
|
||||
@@ -258,54 +277,44 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
|
||||
}, nil)
|
||||
}
|
||||
|
||||
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"))
|
||||
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"))
|
||||
}
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmMerge",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranchName,
|
||||
"selectedBranch": branchName,
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranch,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
err := gui.GitCommand.Merge(branchName)
|
||||
err := gui.GitCommand.Merge(selectedBranch)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
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"))
|
||||
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"))
|
||||
}
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmRebase",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranchName,
|
||||
"selectedBranch": selectedBranch,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
err := gui.GitCommand.RebaseBranch(selectedBranchName)
|
||||
|
||||
err := gui.GitCommand.RebaseBranch(selectedBranch)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
@@ -324,158 +333,22 @@ 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, 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:], "/")
|
||||
|
||||
upstream := "origin" // hardcoding for now
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"Fetching",
|
||||
Teml{
|
||||
"from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName),
|
||||
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
|
||||
"to": branch.Name,
|
||||
},
|
||||
)
|
||||
go func() {
|
||||
_ = gui.createLoaderPanel(gui.g, v, message)
|
||||
|
||||
if gui.State.Panels.Branches.SelectedLine == 0 {
|
||||
if err := gui.GitCommand.PullWithoutPasswordCheck("--ff-only"); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
_ = gui.refreshSidePanels(gui.g)
|
||||
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
} else {
|
||||
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
_ = gui.refreshBranches(gui.g)
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
}
|
||||
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
}()
|
||||
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
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"local-branches": 0,
|
||||
"remotes": 1,
|
||||
"remote-branches": 1,
|
||||
"tags": 2,
|
||||
}
|
||||
|
||||
branchesView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
return gui.refreshBranchesViewWithSelection()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshBranchesViewWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
switch branchesView.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)),
|
||||
)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToBranchMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(branch.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
switch branchesView.Context {
|
||||
case "local-branches":
|
||||
gui.State.Panels.Branches.SelectedLine = selectedLine
|
||||
return gui.handleBranchSelect(gui.g, branchesView)
|
||||
case "remotes":
|
||||
gui.State.Panels.Remotes.SelectedLine = selectedLine
|
||||
return gui.handleRemoteSelect(gui.g, branchesView)
|
||||
case "remote-branches":
|
||||
gui.State.Panels.RemoteBranches.SelectedLine = selectedLine
|
||||
return gui.handleRemoteBranchSelect(gui.g, branchesView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
promptForNewName := func() error {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("NewBranchNamePrompt")+" "+branch.Name+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
newName := gui.trimmedContent(v)
|
||||
if err := gui.GitCommand.RenameBranch(branch.Name, newName); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
// need to checkout so that the branch shows up in our reflog and therefore
|
||||
// doesn't get lost among all the other branches when we switch to something else
|
||||
if err := gui.GitCommand.Checkout(newName, false); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshBranches(gui.g)
|
||||
})
|
||||
}
|
||||
|
||||
// I could do an explicit check here for whether the branch is tracking a remote branch
|
||||
// but if we've selected it we'll already know that via Pullables and Pullables.
|
||||
// Bit of a hack but I'm lazy.
|
||||
notTrackingRemote := branch.Pullables == "?"
|
||||
if notTrackingRemote {
|
||||
return promptForNewName()
|
||||
}
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("renameBranch"), gui.Tr.SLocalize("RenameBranchWarning"), func(_g *gocui.Gui, _v *gocui.View) error {
|
||||
return promptForNewName()
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) currentBranch() *commands.Branch {
|
||||
return gui.State.Branches[0]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
|
||||
@@ -30,34 +29,47 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
if gui.currentViewName() == "commitFiles" {
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
}
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.FocusPoint(0, gui.State.Panels.CommitFiles.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
||||
)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
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
|
||||
}
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
return nil
|
||||
func (gui *Gui) handleCommitFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.CommitFiles
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), false)
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.CommitFiles
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), true)
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.switchFocus(g, v, gui.getCommitsView())
|
||||
commitsView, err := g.View("commits")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchFocus(g, v, commitsView)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -112,11 +124,11 @@ func (gui *Gui) refreshCommitFilesView() error {
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
|
||||
|
||||
commitsFileView := gui.getCommitFilesView()
|
||||
displayStrings := presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles)
|
||||
gui.renderDisplayStrings(commitsFileView, displayStrings)
|
||||
if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, commitsFileView)
|
||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -131,8 +143,7 @@ func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
toggleTheFile := func() error {
|
||||
@@ -187,8 +198,7 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
enterTheFile := func(selectedLineIdx int) error {
|
||||
@@ -198,7 +208,9 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
}
|
||||
}
|
||||
|
||||
gui.changeMainViewsContext("patch-building")
|
||||
if err := gui.changeContext("patch-building"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -214,8 +226,3 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
|
||||
return enterTheFile(selectedLineIdx)
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitFilesPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.CommitFiles.SelectedLine = selectedLine
|
||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
|
||||
_, _ = g.SetViewOnBottom("commitMessage")
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
return gui.switchFocus(g, v, gui.getFilesView())
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
gui.renderString(g, "options", message)
|
||||
return nil
|
||||
return gui.renderString(g, "options", message)
|
||||
}
|
||||
|
||||
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
||||
@@ -84,38 +83,3 @@ 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()
|
||||
}
|
||||
|
||||
+165
-174
@@ -1,11 +1,14 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -20,6 +23,27 @@ 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
|
||||
@@ -34,59 +58,60 @@ 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.handleEscapeLineByLinePanel()
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
v.FocusPoint(0, gui.State.Panels.Commits.SelectedLine)
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if specific diff mode is on, don't show diff
|
||||
if gui.State.Panels.Commits.SpecificDiffMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
commitText, err := gui.GitCommand.Show(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
// I think this is here for the sake of some kind of rebasing thing
|
||||
_ = gui.refreshStatus(g)
|
||||
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
|
||||
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// doing this async because it shouldn't hold anything up
|
||||
go func() {
|
||||
if err := gui.refreshReflogCommits(); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}()
|
||||
v := gui.getCommitsView()
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
|
||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.MainContext == "patch-building") {
|
||||
gui.refreshStatus(g)
|
||||
if g.CurrentView() == v {
|
||||
gui.handleCommitSelect(g, v)
|
||||
}
|
||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.Context == "patch-building") {
|
||||
return gui.refreshCommitFilesView()
|
||||
}
|
||||
return nil
|
||||
@@ -94,29 +119,58 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
return 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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
commits, err := builder.GetCommits(gui.State.Panels.Commits.LimitCommits)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
gui.State.Commits = commits
|
||||
|
||||
if gui.getCommitsView().Context == "branch-commits" {
|
||||
if err := gui.renderBranchCommitsWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, commitView, true, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
|
||||
}
|
||||
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, "mixed"); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.resetOrigin(commitView)
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, commitView)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
@@ -130,12 +184,23 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Squash"), gui.Tr.SLocalize("SureSquashThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Squash"), gui.Tr.SLocalize("SureSquashThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("SquashingStatus"), func() error {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: move to files panel
|
||||
func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool {
|
||||
for _, file := range files {
|
||||
if file.Tracked && file.HasUnstagedChanges {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -151,12 +216,13 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Fixup"), gui.Tr.SLocalize("SureFixupThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Fixup"), gui.Tr.SLocalize("SureFixupThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("FixingStatus"), func() error {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -171,7 +237,7 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.Panels.Commits.SelectedLine != 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
|
||||
}
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.RenameCommit(v.Buffer()); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
@@ -226,6 +292,21 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
// handleMoveTodoDown like handleMidRebaseCommand but for moving an item up in the todo list
|
||||
func (gui *Gui) handleMoveTodoDown(index int) (bool, error) {
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status != "rebasing" {
|
||||
return false, nil
|
||||
}
|
||||
if gui.State.Commits[index+1].Status != "rebasing" {
|
||||
return true, nil
|
||||
}
|
||||
if err := gui.GitCommand.MoveTodoDown(index); err != nil {
|
||||
return true, gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
applied, err := gui.handleMidRebaseCommand("drop")
|
||||
if err != nil {
|
||||
@@ -324,7 +405,7 @@ func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
// at this point we aren't actually rebasing so we will interpret this as an
|
||||
// attempt to pull. We might revoke this later after enabling configurable keybindings
|
||||
return gui.handlePullFiles(g, v)
|
||||
return gui.pullFiles(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -412,7 +493,7 @@ func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
// get selected commit
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
// if already selected commit delete
|
||||
@@ -435,7 +516,7 @@ func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.newStringTask("main", commitText)
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -451,7 +532,7 @@ func (gui *Gui) setDiffMode() {
|
||||
v.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
}
|
||||
|
||||
_ = gui.refreshCommits(gui.g)
|
||||
gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) {
|
||||
@@ -506,107 +587,14 @@ func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) er
|
||||
}, nil)
|
||||
}
|
||||
|
||||
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)
|
||||
type resetOption struct {
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
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))
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
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
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"branch-commits": 0,
|
||||
"reflog-commits": 1,
|
||||
}
|
||||
|
||||
commitsView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
return gui.refreshCommitsViewWithSelection()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitsViewWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
switch commitsView.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)),
|
||||
)
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *resetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -615,30 +603,33 @@ func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
return gui.createResetMenu(commit.Sha)
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitsPanelSearchSelect(selectedLine int) error {
|
||||
commitsView := gui.getCommitsView()
|
||||
switch commitsView.Context {
|
||||
case "branch-commits":
|
||||
gui.State.Panels.Commits.SelectedLine = selectedLine
|
||||
return gui.handleCommitSelect(gui.g, commitsView)
|
||||
case "reflog-commits":
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = selectedLine
|
||||
return gui.handleReflogCommitSelect(gui.g, commitsView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenSearchForCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
// we usually lazyload these commits but now that we're searching we need to load them now
|
||||
if gui.State.Panels.Commits.LimitCommits {
|
||||
gui.State.Panels.Commits.LimitCommits = false
|
||||
if err := gui.refreshCommits(gui.g); err != nil {
|
||||
return err
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
options := make([]*resetOption, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
options[i] = &resetOption{
|
||||
description: fmt.Sprintf("%s reset", strength),
|
||||
command: fmt.Sprintf("reset --%s %s", strength, commit.Sha),
|
||||
}
|
||||
}
|
||||
|
||||
return gui.handleOpenSearch(gui.g, v)
|
||||
handleMenuPress := func(index int) error {
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, strengths[index]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, gui.getCommitsView())
|
||||
}
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
@@ -17,13 +17,11 @@ 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)
|
||||
}
|
||||
}
|
||||
@@ -33,7 +31,6 @@ 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)
|
||||
@@ -67,6 +64,17 @@ 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, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||
gui.onNewPopupPanel()
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, "", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmationView.Editable = true
|
||||
// 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)
|
||||
@@ -75,9 +83,6 @@ 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
|
||||
@@ -97,50 +102,43 @@ func (gui *Gui) onNewPopupPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
gui.Log.Error(err)
|
||||
errMessage := gui.Tr.TemplateLocalize(
|
||||
"CantCloseConfirmationPrompt",
|
||||
Teml{
|
||||
"error": err.Error(),
|
||||
},
|
||||
)
|
||||
gui.Log.Error(errMessage)
|
||||
}
|
||||
}
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
})
|
||||
}()
|
||||
confirmationView.Editable = false
|
||||
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.renderString(g, "confirmation", prompt)
|
||||
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
|
||||
})
|
||||
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",
|
||||
@@ -149,11 +147,17 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
gui.renderString(g, "options", actions)
|
||||
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||
if err := gui.renderString(g, "options", actions); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", 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)
|
||||
}
|
||||
|
||||
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||
|
||||
+38
-12
@@ -1,19 +1,45 @@
|
||||
package gui
|
||||
|
||||
// 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
|
||||
func (gui *Gui) changeContext(context string) error {
|
||||
oldContext := gui.State.Context
|
||||
|
||||
if gui.State.Context == context {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch context {
|
||||
case "normal", "patch-building", "staging", "merging":
|
||||
gui.getMainView().Context = context
|
||||
gui.getSecondaryView().Context = context
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.MainContext = 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
|
||||
}
|
||||
|
||||
@@ -82,8 +82,7 @@ func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
gui.renderString(g, "options", message)
|
||||
return nil
|
||||
return gui.renderString(g, "options", message)
|
||||
}
|
||||
|
||||
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
displayString: gui.Tr.SLocalize("discardAllChanges"),
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
menuItems = append(menuItems, &menuItem{
|
||||
displayString: gui.Tr.SLocalize("discardUnstagedChanges"),
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return gui.createMenu(file.Name, menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
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 {
|
||||
// TODO: get this going again, and ensure we don't see any crashes from it
|
||||
return &fileWatcher{
|
||||
Disabled: true,
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
+280
-166
@@ -10,9 +10,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -26,69 +27,82 @@ func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
|
||||
return gui.State.Files[selectedLine], nil
|
||||
}
|
||||
|
||||
func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
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)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
gui.State.SplitMainPanel = false
|
||||
gui.getMainView().Title = ""
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
}
|
||||
|
||||
gui.getFilesView().FocusPoint(0, gui.State.Panels.Files.SelectedLine)
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("MergeConflictsTitle")
|
||||
gui.State.SplitMainPanel = false
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
if !alreadySelected {
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getSecondaryView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
content := gui.GitCommand.Diff(file, false, false)
|
||||
contentCached := gui.GitCommand.Diff(file, false, true)
|
||||
leftContent := content
|
||||
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.newPtyTask("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")
|
||||
}
|
||||
}
|
||||
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return gui.renderString(g, "main", leftContent)
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -101,13 +115,19 @@ func (gui *Gui) refreshFiles() error {
|
||||
}
|
||||
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
displayStrings := presentation.GetFileListDisplayStrings(gui.State.Files)
|
||||
gui.renderDisplayStrings(filesView, displayStrings)
|
||||
|
||||
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
||||
filesView.Clear()
|
||||
isFocused := gui.g.CurrentView().Name() == "files"
|
||||
list, err := utils.RenderList(gui.State.Files, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(filesView, list)
|
||||
|
||||
if filesView == g.CurrentView() {
|
||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
||||
return gui.selectFile(alreadySelected)
|
||||
return gui.handleFileSelect(g, filesView, alreadySelected)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -115,6 +135,28 @@ 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 {
|
||||
@@ -165,7 +207,9 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
|
||||
}
|
||||
gui.changeMainViewsContext("staging")
|
||||
if err := gui.changeContext("staging"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -186,19 +230,16 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
if file.HasUnstagedChanges {
|
||||
err = gui.GitCommand.StageFile(file.Name)
|
||||
gui.GitCommand.StageFile(file.Name)
|
||||
} else {
|
||||
err = gui.GitCommand.UnStageFile(file.Name, file.Tracked)
|
||||
}
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
gui.GitCommand.UnStageFile(file.Name, file.Tracked)
|
||||
}
|
||||
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.selectFile(true)
|
||||
return gui.handleFileSelect(g, v, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) allFilesStaged() bool {
|
||||
@@ -210,14 +251,6 @@ 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() {
|
||||
@@ -233,33 +266,20 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.selectFile(false)
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
if file.Tracked {
|
||||
return gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("IgnoreTracked"), gui.Tr.SLocalize("IgnoreTrackedPrompt"),
|
||||
// On confirmation
|
||||
func(_ *gocui.Gui, _ *gocui.View) error {
|
||||
if err := gui.GitCommand.Ignore(file.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.GitCommand.RemoveTrackedFiles(file.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshFiles()
|
||||
}, nil)
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantIgnoreTrackFiles"))
|
||||
}
|
||||
|
||||
if err := gui.GitCommand.Ignore(file.Name); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
@@ -269,7 +289,9 @@ func (gui *Gui) handleWIPCommitPress(g *gocui.Gui, filesView *gocui.View) error
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
|
||||
}
|
||||
|
||||
gui.renderString(g, "commitMessage", skipHookPreifx)
|
||||
if err := gui.renderString(g, "commitMessage", skipHookPreifx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -283,14 +305,8 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
}
|
||||
commitMessageView := gui.getCommitMessageView()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
if _, err := g.SetViewOnTop("commitMessage"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.switchFocus(g, filesView, commitMessageView); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.SetViewOnTop("commitMessage")
|
||||
gui.switchFocus(g, filesView, commitMessageView)
|
||||
gui.RenderCommitLength()
|
||||
return nil
|
||||
})
|
||||
@@ -369,11 +385,6 @@ 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()
|
||||
}
|
||||
@@ -384,75 +395,43 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return "", err
|
||||
}
|
||||
return "", gui.newStringTask("main", gui.Tr.SLocalize("NoFilesDisplay"))
|
||||
return "", gui.renderString(g, "main", gui.Tr.SLocalize("NoFilesDisplay"))
|
||||
}
|
||||
if item.Type != "file" {
|
||||
return "", gui.newStringTask("main", gui.Tr.SLocalize("NotAFile"))
|
||||
return "", gui.renderString(g, "main", gui.Tr.SLocalize("NotAFile"))
|
||||
}
|
||||
cat, err := gui.GitCommand.CatFile(item.Name)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return "", gui.newStringTask("main", err.Error())
|
||||
return "", gui.renderString(g, "main", err.Error())
|
||||
}
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
// if we have no upstream branch we need to set that first
|
||||
currentBranch := gui.currentBranch()
|
||||
if currentBranch.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 == currentBranch.Name {
|
||||
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranch.Name, func(g *gocui.Gui, v *gocui.View) error {
|
||||
upstream := gui.trimmedContent(v)
|
||||
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
|
||||
errorMessage := err.Error()
|
||||
if strings.Contains(errorMessage, "does not exist") {
|
||||
errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
|
||||
}
|
||||
return gui.createErrorPanel(gui.g, errorMessage)
|
||||
}
|
||||
return gui.pullFiles(v, "")
|
||||
})
|
||||
}
|
||||
|
||||
return gui.pullFiles(v, "")
|
||||
}
|
||||
|
||||
func (gui *Gui) pullFiles(v *gocui.View, args string) error {
|
||||
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PullWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
unamePassOpend := false
|
||||
err := gui.GitCommand.Pull(args, func(passOrUname string) string {
|
||||
err := gui.GitCommand.Pull(func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(gui.g, v, passOrUname)
|
||||
return gui.waitForPassUname(g, v, passOrUname)
|
||||
})
|
||||
gui.HandleCredentialsPopup(gui.g, unamePassOpend, err)
|
||||
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstream string, args string) error {
|
||||
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PushWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
unamePassOpend := false
|
||||
branchName := gui.getCheckedOutBranch().Name
|
||||
err := gui.GitCommand.Push(branchName, force, upstream, args, func(passOrUname string) string {
|
||||
branchName := gui.State.Branches[0].Name
|
||||
err := gui.GitCommand.Push(branchName, force, func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(g, v, passOrUname)
|
||||
})
|
||||
@@ -463,29 +442,14 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstr
|
||||
|
||||
func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
// if we have pullables we'll ask if the user wants to force push
|
||||
currentBranch := gui.currentBranch()
|
||||
|
||||
if currentBranch.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 == currentBranch.Name {
|
||||
return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+currentBranch.Name, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(g, v, false, gui.trimmedContent(v), "")
|
||||
})
|
||||
} else if currentBranch.Pullables == "0" {
|
||||
return gui.pushWithForceFlag(g, v, false, "", "")
|
||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
if pullables == "?" || pullables == "0" {
|
||||
return gui.pushWithForceFlag(g, v, false)
|
||||
}
|
||||
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, "", "")
|
||||
err := 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)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -499,13 +463,24 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
if !file.HasInlineMergeConflicts {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons"))
|
||||
}
|
||||
gui.changeMainViewsContext("merging")
|
||||
if err := gui.changeContext("merging"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.AbortMerge(); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("MergeAborted"))
|
||||
gui.refreshStatus(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) openFile(filename string) error {
|
||||
if err := gui.OSCommand.OpenFile(filename); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
@@ -522,42 +497,181 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type discardOption struct {
|
||||
handler func(fileName *commands.File) error
|
||||
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 {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
options := []*discardOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardAllFileChanges(file)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func(file *commands.File) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
discardUnstagedChanges := &discardOption{
|
||||
description: gui.Tr.SLocalize("discardUnstagedChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardUnstagedFileChanges(file)
|
||||
},
|
||||
}
|
||||
|
||||
options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...)
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := options[index].handler(file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
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 {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
command := gui.trimmedContent(v)
|
||||
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
|
||||
return gui.Errors.ErrSubProcess
|
||||
})
|
||||
}
|
||||
|
||||
type stashOption struct {
|
||||
description string
|
||||
handler func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *stashOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateStashMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
menuItems := []*menuItem{
|
||||
options := []*stashOption{
|
||||
{
|
||||
displayString: gui.Tr.SLocalize("stashAllChanges"),
|
||||
onPress: func() error {
|
||||
description: gui.Tr.SLocalize("stashAllChanges"),
|
||||
handler: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
},
|
||||
},
|
||||
{
|
||||
displayString: gui.Tr.SLocalize("stashStagedChanges"),
|
||||
onPress: func() error {
|
||||
description: gui.Tr.SLocalize("stashStagedChanges"),
|
||||
handler: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), menuItems, createMenuOptions{showCancel: true})
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToUpstreamMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createResetMenu("@{upstream}")
|
||||
}
|
||||
|
||||
func (gui *Gui) onFilesPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.Files.SelectedLine = selectedLine
|
||||
return gui.focusAndSelectFile(gui.g, gui.getFilesView())
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
// not localising here because it's one to one with the actual git flow commands
|
||||
displayString: fmt.Sprintf("finish branch '%s'", branch.Name),
|
||||
onPress: func() error {
|
||||
return gui.gitFlowFinishBranch(gitFlowConfig, branch.Name)
|
||||
},
|
||||
},
|
||||
{
|
||||
displayString: "start feature",
|
||||
onPress: startHandler("feature"),
|
||||
},
|
||||
{
|
||||
displayString: "start hotfix",
|
||||
onPress: startHandler("hotfix"),
|
||||
},
|
||||
{
|
||||
displayString: "start release",
|
||||
onPress: startHandler("release"),
|
||||
},
|
||||
}
|
||||
|
||||
return gui.createMenu("git flow", menuItems, createMenuOptions{})
|
||||
}
|
||||
+174
-418
@@ -5,7 +5,6 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
// "io"
|
||||
@@ -25,20 +24,12 @@ 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"
|
||||
)
|
||||
|
||||
const (
|
||||
SCREEN_NORMAL int = iota
|
||||
SCREEN_HALF
|
||||
SCREEN_FULL
|
||||
)
|
||||
|
||||
const StartupPopupVersion = 1
|
||||
|
||||
// OverlappingEdges determines if panel edges overlap
|
||||
@@ -75,22 +66,19 @@ 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
|
||||
fileWatcher *fileWatcher
|
||||
viewBufferManagerMap map[string]*tasks.ViewBufferManager
|
||||
stopChan chan 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
|
||||
}
|
||||
|
||||
// for now the staging panel state, unlike the other panel states, is going to be
|
||||
@@ -117,31 +105,13 @@ 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 {
|
||||
@@ -163,56 +133,33 @@ type statusPanelState struct {
|
||||
}
|
||||
|
||||
type panelStates struct {
|
||||
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 searchingState struct {
|
||||
view *gocui.View
|
||||
isSearching bool
|
||||
searchString string
|
||||
Files *filePanelState
|
||||
Branches *branchPanelState
|
||||
Commits *commitPanelState
|
||||
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
|
||||
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
|
||||
Searching searchingState
|
||||
ScreenMode int
|
||||
SideView *gocui.View
|
||||
Ptmx *os.File
|
||||
PrevMainWidth int
|
||||
PrevMainHeight int
|
||||
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
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
@@ -220,7 +167,7 @@ type guiState struct {
|
||||
// NewGui builds a new gui handler
|
||||
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
|
||||
|
||||
initialState := &guiState{
|
||||
initialState := guiState{
|
||||
Files: make([]*commands.File, 0),
|
||||
PreviousView: "files",
|
||||
Commits: make([]*commands.Commit, 0),
|
||||
@@ -229,16 +176,12 @@ 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},
|
||||
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},
|
||||
Files: &filePanelState{SelectedLine: -1},
|
||||
Branches: &branchPanelState{SelectedLine: 0},
|
||||
Commits: &commitPanelState{SelectedLine: -1},
|
||||
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
||||
Stash: &stashPanelState{SelectedLine: -1},
|
||||
Menu: &menuPanelState{SelectedLine: 0},
|
||||
Merging: &mergingPanelState{
|
||||
ConflictIndex: 0,
|
||||
ConflictTop: true,
|
||||
@@ -247,58 +190,24 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
},
|
||||
Status: &statusPanelState{},
|
||||
},
|
||||
ScreenMode: SCREEN_NORMAL,
|
||||
SideView: nil,
|
||||
Ptmx: nil,
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: oSCommand,
|
||||
State: initialState,
|
||||
Config: config,
|
||||
Tr: tr,
|
||||
Updater: updater,
|
||||
statusManager: &statusManager{},
|
||||
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: oSCommand,
|
||||
State: initialState,
|
||||
Config: config,
|
||||
Tr: tr,
|
||||
Updater: updater,
|
||||
statusManager: &statusManager{},
|
||||
}
|
||||
|
||||
gui.watchFilesForChanges()
|
||||
|
||||
gui.GenerateSentinelErrors()
|
||||
|
||||
return gui, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ScreenMode = utils.NextIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
// commits render differently depending on whether we're in fullscreen more or not
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
// same with branches
|
||||
if err := gui.refreshBranchesViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ScreenMode = utils.PrevIntInCycle([]int{SCREEN_NORMAL, SCREEN_HALF, SCREEN_FULL}, gui.State.ScreenMode)
|
||||
// commits render differently depending on whether we're in fullscreen more or not
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
// same with branches
|
||||
if err := gui.refreshBranchesViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpView(viewName string) error {
|
||||
mainView, _ := gui.g.View(viewName)
|
||||
ox, oy := mainView.Origin()
|
||||
@@ -314,14 +223,8 @@ func (gui *Gui) scrollDownView(viewName string) error {
|
||||
_, sy := mainView.Size()
|
||||
y += sy
|
||||
}
|
||||
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)
|
||||
if y < len(mainView.BufferLines()) {
|
||||
return mainView.SetOrigin(ox, oy+gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -387,17 +290,20 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if v.IsSearching() && newView.Name() != "search" {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
switch v.Name() {
|
||||
case "main":
|
||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||
gui.changeMainViewsContext("normal")
|
||||
if err := gui.changeContext("normal"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "commitFiles":
|
||||
if gui.State.MainContext != "patch-building" {
|
||||
if gui.State.Context != "patch-building" {
|
||||
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -415,75 +321,6 @@ func (gui *Gui) onFocus(v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getViewHeights() map[string]int {
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.State.PreviousView
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviousView := true
|
||||
for _, view := range cyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviousView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviousView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
|
||||
// unfortunate result of the fact that these are separate views, have to map explicitly
|
||||
if currentCyclebleView == "commitFiles" {
|
||||
currentCyclebleView = "commits"
|
||||
}
|
||||
|
||||
_, height := gui.g.Size()
|
||||
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
vHeights := map[string]int{
|
||||
"status": 0,
|
||||
"files": 0,
|
||||
"branches": 0,
|
||||
"commits": 0,
|
||||
"stash": 0,
|
||||
"options": 0,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - 1
|
||||
return vHeights
|
||||
}
|
||||
|
||||
usableSpace := height - 7
|
||||
extraSpace := usableSpace - (usableSpace/3)*3
|
||||
|
||||
if height >= 28 {
|
||||
return map[string]int{
|
||||
"status": 3,
|
||||
"files": (usableSpace / 3) + extraSpace,
|
||||
"branches": usableSpace / 3,
|
||||
"commits": usableSpace / 3,
|
||||
"stash": 3,
|
||||
"options": 1,
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights := map[string]int{
|
||||
"status": defaultHeight,
|
||||
"files": defaultHeight,
|
||||
"branches": defaultHeight,
|
||||
"commits": defaultHeight,
|
||||
"stash": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
|
||||
return vHeights
|
||||
}
|
||||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
g.Highlight = true
|
||||
@@ -510,7 +347,50 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
vHeights := gui.getViewHeights()
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.State.PreviousView
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviouseView := true
|
||||
for _, view := range cyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviouseView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviouseView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
|
||||
usableSpace := height - 7
|
||||
extraSpace := usableSpace - (usableSpace/3)*3
|
||||
|
||||
vHeights := map[string]int{
|
||||
"status": 3,
|
||||
"files": (usableSpace / 3) + extraSpace,
|
||||
"branches": usableSpace / 3,
|
||||
"commits": usableSpace / 3,
|
||||
"stash": 3,
|
||||
"options": 1,
|
||||
}
|
||||
|
||||
if height < 28 {
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights = map[string]int{
|
||||
"status": defaultHeight,
|
||||
"files": defaultHeight,
|
||||
"branches": defaultHeight,
|
||||
"commits": defaultHeight,
|
||||
"stash": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
}
|
||||
|
||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||
|
||||
@@ -520,50 +400,21 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
appStatusOptionsBoundary = len(appStatus) + 2
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
_ = g.DeleteView("limit")
|
||||
|
||||
sidePanelWidthRatio := gui.Config.GetUserConfig().GetFloat64("gui.sidePanelWidth")
|
||||
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
var leftSideWidth int
|
||||
switch gui.State.ScreenMode {
|
||||
case SCREEN_NORMAL:
|
||||
leftSideWidth = int(float64(width) * sidePanelWidthRatio)
|
||||
case SCREEN_HALF:
|
||||
leftSideWidth = width / 2
|
||||
case SCREEN_FULL:
|
||||
currentView := gui.g.CurrentView()
|
||||
if currentView != nil && currentView.Name() == "main" {
|
||||
leftSideWidth = 0
|
||||
} else {
|
||||
leftSideWidth = width - 1
|
||||
}
|
||||
panelSpacing := 1
|
||||
if OverlappingEdges {
|
||||
panelSpacing = 0
|
||||
}
|
||||
|
||||
mainPanelLeft := leftSideWidth + 1
|
||||
mainPanelRight := width - 1
|
||||
secondaryPanelLeft := width - 1
|
||||
secondaryPanelTop := 0
|
||||
mainPanelBottom := height - 2
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
g.DeleteView("limit")
|
||||
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
leftSideWidth := width / 3
|
||||
panelSplitX := width - 1
|
||||
if gui.State.SplitMainPanel {
|
||||
if gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainPanelLeft = 0
|
||||
panelSplitX := width/2 - 4
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
} else if width < 220 {
|
||||
mainPanelBottom = height/2 - 1
|
||||
secondaryPanelTop = mainPanelBottom + 1
|
||||
secondaryPanelLeft = leftSideWidth + 1
|
||||
} else {
|
||||
units := 5
|
||||
leftSideWidth = width / units
|
||||
mainPanelLeft = leftSideWidth + 1
|
||||
panelSplitX := (1 + ((units - 1) / 2)) * width / units
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
}
|
||||
units := 7
|
||||
leftSideWidth = width / units
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
}
|
||||
|
||||
main := "main"
|
||||
@@ -574,22 +425,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
secondary = "main"
|
||||
}
|
||||
|
||||
// reading more lines into main view buffers upon resize
|
||||
prevMainView, err := gui.g.View("main")
|
||||
if err == nil {
|
||||
_, prevMainHeight := prevMainView.Size()
|
||||
heightDiff := mainPanelBottom - prevMainHeight - 1
|
||||
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, mainPanelLeft, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, panelSplitX, height-2, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -597,16 +433,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
v.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
v.Wrap = true
|
||||
v.FgColor = textColor
|
||||
v.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
hiddenViewOffset := 9999
|
||||
|
||||
hiddenSecondaryPanelOffset := 0
|
||||
hiddenViewOffset := 0
|
||||
if !gui.State.SplitMainPanel {
|
||||
hiddenSecondaryPanelOffset = hiddenViewOffset
|
||||
hiddenViewOffset = 9999
|
||||
}
|
||||
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenSecondaryPanelOffset, hiddenSecondaryPanelOffset+secondaryPanelTop, width-1+hiddenSecondaryPanelOffset, height-2+hiddenSecondaryPanelOffset, gocui.LEFT)
|
||||
secondaryView, err := g.SetView(secondary, panelSplitX+1+hiddenViewOffset, hiddenViewOffset, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -614,7 +447,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
secondaryView.Wrap = true
|
||||
secondaryView.FgColor = gocui.ColorWhite
|
||||
secondaryView.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
@@ -632,8 +464,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
filesView.Highlight = true
|
||||
filesView.Title = gui.Tr.SLocalize("FilesTitle")
|
||||
filesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onFilesPanelSearchSelect))
|
||||
filesView.ContainsList = true
|
||||
v.FgColor = textColor
|
||||
}
|
||||
|
||||
branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
|
||||
@@ -642,10 +473,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
|
||||
branchesView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onBranchesPanelSearchSelect))
|
||||
branchesView.ContainsList = true
|
||||
}
|
||||
|
||||
if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
|
||||
@@ -654,8 +482,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("CommitFiles")
|
||||
v.FgColor = textColor
|
||||
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
|
||||
v.ContainsList = true
|
||||
}
|
||||
|
||||
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
||||
@@ -664,10 +490,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
|
||||
commitsView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitsPanelSearchSelect))
|
||||
commitsView.ContainsList = true
|
||||
}
|
||||
|
||||
stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
|
||||
@@ -677,8 +500,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
stashView.Title = gui.Tr.SLocalize("StashTitle")
|
||||
stashView.FgColor = textColor
|
||||
stashView.SetOnSelectItem(gui.onSelectItemWrapper(gui.onStashPanelSearchSelect))
|
||||
stashView.ContainsList = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
|
||||
@@ -686,26 +507,26 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
v.FgColor = theme.OptionsColor
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
v.FgColor = theme.GetColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
}
|
||||
|
||||
if gui.getCommitMessageView() == nil {
|
||||
// doesn't matter where this view starts because it will be hidden
|
||||
if commitMessageView, err := g.SetView("commitMessage", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
|
||||
if commitMessageView, err := g.SetView("commitMessage", width, height, width*2, height*2, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
_, _ = g.SetViewOnBottom("commitMessage")
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
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", hiddenViewOffset, hiddenViewOffset, hiddenViewOffset+10, hiddenViewOffset+10, 0); err != nil {
|
||||
if credentialsView, err := g.SetView("credentials", width, height, width*2, height*2, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
@@ -719,35 +540,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
searchViewOffset := hiddenViewOffset
|
||||
if gui.State.Searching.isSearching {
|
||||
searchViewOffset = 0
|
||||
}
|
||||
|
||||
// this view takes up one character. Its only purpose is to show the slash when searching
|
||||
searchPrefix := "search: "
|
||||
if searchPrefixView, err := g.SetView("searchPrefix", appStatusOptionsBoundary-1+searchViewOffset, height-2+searchViewOffset, len(searchPrefix)+searchViewOffset, height+searchViewOffset, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
|
||||
searchPrefixView.BgColor = gocui.ColorDefault
|
||||
searchPrefixView.FgColor = gocui.ColorGreen
|
||||
searchPrefixView.Frame = false
|
||||
gui.setViewContent(gui.g, searchPrefixView, searchPrefix)
|
||||
}
|
||||
|
||||
if searchView, err := g.SetView("search", appStatusOptionsBoundary-1+searchViewOffset+len(searchPrefix), height-2+searchViewOffset, optionsVersionBoundary+searchViewOffset, height+searchViewOffset, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
|
||||
searchView.BgColor = gocui.ColorDefault
|
||||
searchView.FgColor = gocui.ColorGreen
|
||||
searchView.Frame = false
|
||||
searchView.Editable = true
|
||||
}
|
||||
|
||||
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
@@ -767,10 +559,12 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
gui.renderString(g, "information", information)
|
||||
if err := gui.renderString(g, "information", information); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// doing this here because it'll only happen once
|
||||
if err := gui.onInitialViewsCreation(); err != nil {
|
||||
if err := gui.loadNewRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -788,38 +582,22 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
type listViewState struct {
|
||||
selectedLine int
|
||||
lineCount int
|
||||
view *gocui.View
|
||||
context string
|
||||
}
|
||||
|
||||
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)},
|
||||
listViews := map[*gocui.View]listViewState{
|
||||
filesView: {selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
|
||||
branchesView: {selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
|
||||
commitsView: {selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
|
||||
stashView: {selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries)},
|
||||
}
|
||||
|
||||
// menu view might not exist so we check to be safe
|
||||
if menuView, err := gui.g.View("menu"); err == nil {
|
||||
listViews = append(listViews, listViewState{view: menuView, context: "", selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount})
|
||||
listViews[menuView] = listViewState{selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}
|
||||
}
|
||||
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
|
||||
}
|
||||
for view, state := range listViews {
|
||||
// check if the selected line is now out of view and if so refocus it
|
||||
listView.view.FocusPoint(0, listView.selectedLine)
|
||||
}
|
||||
|
||||
mainViewWidth, mainViewHeight := gui.getMainView().Size()
|
||||
if mainViewWidth != gui.State.PrevMainWidth || mainViewHeight != gui.State.PrevMainHeight {
|
||||
gui.State.PrevMainWidth = mainViewWidth
|
||||
gui.State.PrevMainHeight = mainViewHeight
|
||||
if err := gui.onResize(); err != nil {
|
||||
if err := gui.focusPoint(0, state.selectedLine, state.lineCount, view); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -831,15 +609,6 @@ 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 {
|
||||
@@ -907,32 +676,32 @@ func (gui *Gui) fetch(g *gocui.Gui, v *gocui.View, canAskForCredentials bool) (u
|
||||
_ = gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Error"), coloredMessage, close, close)
|
||||
}
|
||||
|
||||
_ = gui.refreshStatus(g)
|
||||
|
||||
gui.refreshStatus(g)
|
||||
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{
|
||||
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"),
|
||||
gui.getKeyDisplay("universal.optionMenu"): gui.Tr.SLocalize("menu"),
|
||||
"1-5": gui.Tr.SLocalize("jump"),
|
||||
"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"),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_ = function()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
for range time.Tick(interval) {
|
||||
_ = function()
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -947,7 +716,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, gui.stopChan, func() error {
|
||||
gui.goEvery(time.Second*60, func() error {
|
||||
_, err := gui.fetch(gui.g, gui.g.CurrentView(), false)
|
||||
return err
|
||||
})
|
||||
@@ -956,21 +725,12 @@ func (gui *Gui) startBackgroundFetch() {
|
||||
|
||||
// Run setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run() error {
|
||||
g, err := gocui.NewGui(gocui.Output256, OverlappingEdges)
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.OnSearchEscape = gui.onSearchEscape
|
||||
g.SearchEscapeKey = gui.getKey("universal.return")
|
||||
g.NextSearchMatchKey = gui.getKey("universal.nextMatch")
|
||||
g.PrevSearchMatchKey = gui.getKey("universal.prevMatch")
|
||||
|
||||
gui.stopChan = make(chan struct{})
|
||||
|
||||
g.ASCII = runtime.GOOS == "windows" && runewidth.IsEastAsian()
|
||||
|
||||
if gui.Config.GetUserConfig().GetBool("gui.mouseEvents") {
|
||||
g.Mouse = true
|
||||
}
|
||||
@@ -997,7 +757,8 @@ func (gui *Gui) Run() error {
|
||||
go gui.startBackgroundFetch()
|
||||
}
|
||||
|
||||
gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFiles)
|
||||
gui.goEvery(time.Second*10, gui.refreshFiles)
|
||||
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
|
||||
|
||||
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
||||
|
||||
@@ -1017,24 +778,7 @@ 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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
} else if err == gui.Errors.ErrSwitchRepo {
|
||||
continue
|
||||
@@ -1074,6 +818,18 @@ func (gui *Gui) runCommand() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.Updating {
|
||||
return gui.createUpdateQuitConfirmation(g, v)
|
||||
}
|
||||
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
|
||||
return gui.createConfirmationPanel(g, v, true, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.g.Mouse {
|
||||
return nil
|
||||
|
||||
+578
-1113
File diff suppressed because it is too large
Load Diff
@@ -88,8 +88,7 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second
|
||||
}
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil))
|
||||
return nil
|
||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil))
|
||||
})
|
||||
|
||||
return false, nil
|
||||
@@ -221,18 +220,14 @@ func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCycleLine(1)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedCommitFileName() string {
|
||||
return gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainView() error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
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.MainContext == "patch-building" {
|
||||
filename := gui.getSelectedCommitFileName()
|
||||
if gui.State.Context == "patch-building" {
|
||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
}
|
||||
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
||||
@@ -242,8 +237,7 @@ func (gui *Gui) refreshMainView() error {
|
||||
mainView.Wrap = false
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
gui.setViewContent(gui.g, gui.getMainView(), colorDiff)
|
||||
return nil
|
||||
return gui.setViewContent(gui.g, gui.getMainView(), colorDiff)
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -320,8 +314,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
+38
-41
@@ -8,26 +8,33 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type menuItem struct {
|
||||
displayString string
|
||||
displayStrings []string
|
||||
onPress func() error
|
||||
}
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
v.FocusPoint(0, gui.State.Panels.Menu.SelectedLine)
|
||||
return nil
|
||||
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{
|
||||
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"),
|
||||
"esc/q": gui.Tr.SLocalize("close"),
|
||||
"↑ ↓": gui.Tr.SLocalize("navigate"),
|
||||
"space": gui.Tr.SLocalize("execute"),
|
||||
}
|
||||
return gui.renderOptionsMap(optionsMap)
|
||||
}
|
||||
@@ -45,49 +52,27 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.returnFocus(g, v)
|
||||
}
|
||||
|
||||
type createMenuOptions struct {
|
||||
showCancel bool
|
||||
}
|
||||
|
||||
func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions createMenuOptions) error {
|
||||
if createMenuOptions.showCancel {
|
||||
// this is mutative but I'm okay with that for now
|
||||
items = append(items, &menuItem{
|
||||
displayStrings: []string{gui.Tr.SLocalize("cancel")},
|
||||
onPress: func() error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
|
||||
isFocused := gui.g.CurrentView().Name() == "menu"
|
||||
gui.State.MenuItemCount = itemCount
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.MenuItemCount = len(items)
|
||||
|
||||
stringArrays := make([][]string, len(items))
|
||||
for i, item := range items {
|
||||
if item.displayStrings == nil {
|
||||
stringArrays[i] = []string{item.displayString}
|
||||
} else {
|
||||
stringArrays[i] = item.displayStrings
|
||||
}
|
||||
}
|
||||
|
||||
list := utils.RenderDisplayStrings(stringArrays)
|
||||
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list)
|
||||
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
|
||||
menuView.Title = title
|
||||
menuView.FgColor = theme.GocuiDefaultTextColor
|
||||
menuView.ContainsList = true
|
||||
menuView.Clear()
|
||||
fmt.Fprint(menuView, list)
|
||||
gui.State.Panels.Menu.SelectedLine = 0
|
||||
|
||||
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedLine := gui.State.Panels.Menu.SelectedLine
|
||||
if err := items[selectedLine].onPress(); err != nil {
|
||||
if err := handlePress(selectedLine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := gui.g.View("menu"); err == nil {
|
||||
if _, err := gui.g.SetViewOnBottom("menu"); err != nil {
|
||||
return err
|
||||
@@ -102,7 +87,7 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr
|
||||
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
|
||||
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
|
||||
|
||||
if err := gui.g.SetKeybinding("menu", nil, key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -118,3 +103,15 @@ func (gui *Gui) createMenu(title string, items []*menuItem, createMenuOptions cr
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
+14
-22
@@ -5,7 +5,6 @@ package gui
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
@@ -151,19 +150,13 @@ func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644)
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||
if err := gui.pushFileSnapshot(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.pushFileSnapshot(g)
|
||||
pick := "bottom"
|
||||
if gui.State.Panels.Merging.ConflictTop {
|
||||
pick = "top"
|
||||
@@ -184,9 +177,7 @@ func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||
if err := gui.pushFileSnapshot(g); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.pushFileSnapshot(g)
|
||||
err := gui.resolveConflict(g, conflict, "both")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -220,15 +211,16 @@ func (gui *Gui) refreshMergePanel() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainView := gui.getMainView()
|
||||
mainView.Wrap = false
|
||||
gui.setViewContent(gui.g, mainView, content)
|
||||
gui.Log.Warn("scrolling to conflict")
|
||||
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
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -251,11 +243,11 @@ func (gui *Gui) scrollToConflict(g *gocui.Gui) error {
|
||||
|
||||
func (gui *Gui) renderMergeOptions() error {
|
||||
return gui.renderOptionsMap(map[string]string{
|
||||
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"),
|
||||
"↑ ↓": gui.Tr.SLocalize("selectHunk"),
|
||||
"← →": gui.Tr.SLocalize("navigateConflicts"),
|
||||
"space": gui.Tr.SLocalize("pickHunk"),
|
||||
"b": gui.Tr.SLocalize("pickBothHunks"),
|
||||
"z": gui.Tr.SLocalize("undo"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package gui
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
@@ -12,17 +13,15 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
bindingsGlobal, bindingsPanel []*Binding
|
||||
)
|
||||
|
||||
bindings := gui.GetInitialKeybindings()
|
||||
bindings := gui.GetCurrentKeybindings()
|
||||
|
||||
for _, binding := range bindings {
|
||||
if GetKeyDisplay(binding.Key) != "" && binding.Description != "" {
|
||||
if binding.GetKey() != "" && binding.Description != "" {
|
||||
switch binding.ViewName {
|
||||
case "":
|
||||
bindingsGlobal = append(bindingsGlobal, binding)
|
||||
case v.Name():
|
||||
if len(binding.Contexts) == 0 || utils.IncludesString(binding.Contexts, v.Context) {
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
}
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,23 +35,19 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
bindings := gui.getBindings(v)
|
||||
|
||||
menuItems := make([]*menuItem, len(bindings))
|
||||
|
||||
for i, binding := range bindings {
|
||||
innerBinding := binding // note to self, never close over loop variables
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{GetKeyDisplay(innerBinding.Key), innerBinding.Description},
|
||||
onPress: func() error {
|
||||
if innerBinding.Key == nil {
|
||||
return nil
|
||||
}
|
||||
if err := gui.handleMenuClose(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return innerBinding.Handler(g, v)
|
||||
},
|
||||
handleMenuPress := func(index int) error {
|
||||
if bindings[index].Key == nil {
|
||||
return nil
|
||||
}
|
||||
if index >= len(bindings) {
|
||||
return errors.New("Index is greater than size of bindings")
|
||||
}
|
||||
err := gui.handleMenuClose(g, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bindings[index].Handler(g, v)
|
||||
}
|
||||
|
||||
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), menuItems, createMenuOptions{})
|
||||
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, len(bindings), handleMenuPress)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
@@ -18,8 +17,7 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
// get diff from commit file that's currently selected
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
diff, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true)
|
||||
@@ -44,25 +42,38 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) handleAddSelectionToPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
toggleFunc := gui.GitCommand.PatchManager.AddFileLineRange
|
||||
filename := gui.getSelectedCommitFileName()
|
||||
includedLineIndices := gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.SelectedLineIdx)
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = gui.GitCommand.PatchManager.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
toggleFunc(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
gui.GitCommand.PatchManager.AddFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.RemoveFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
@@ -76,7 +87,8 @@ func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapePatchBuildingPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
gui.State.Panels.LineByLine = nil
|
||||
gui.changeContext("normal")
|
||||
|
||||
if gui.GitCommand.PatchManager.IsEmpty() {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
@@ -94,8 +106,7 @@ func (gui *Gui) refreshSecondaryPatchPanel() error {
|
||||
secondaryView.Wrap = false
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
gui.setViewContent(gui.g, gui.getSecondaryView(), gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false))
|
||||
return nil
|
||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false))
|
||||
})
|
||||
} else {
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
@@ -6,43 +6,48 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type patchMenuOption struct {
|
||||
displayName string
|
||||
function func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *patchMenuOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.displayName}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoPatchError"))
|
||||
}
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha),
|
||||
onPress: gui.handleDeletePatchFromCommit,
|
||||
},
|
||||
{
|
||||
displayString: "pull patch out into index",
|
||||
onPress: gui.handlePullPatchIntoWorkingTree,
|
||||
},
|
||||
{
|
||||
displayString: "reset patch",
|
||||
onPress: gui.handleResetPatch,
|
||||
},
|
||||
options := []*patchMenuOption{
|
||||
{displayName: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha), function: gui.handleDeletePatchFromCommit},
|
||||
{displayName: "pull patch out into index", function: gui.handlePullPatchIntoWorkingTree},
|
||||
{displayName: "reset patch", function: gui.handleResetPatch},
|
||||
}
|
||||
|
||||
selectedCommit := gui.getSelectedCommit(gui.g)
|
||||
if selectedCommit != nil && gui.GitCommand.PatchManager.CommitSha != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
menuItems = append(
|
||||
menuItems[:1],
|
||||
options = append(
|
||||
options[:1],
|
||||
append(
|
||||
[]*menuItem{
|
||||
[]*patchMenuOption{
|
||||
{
|
||||
displayString: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
onPress: gui.handleMovePatchToSelectedCommit,
|
||||
displayName: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
function: gui.handleMovePatchToSelectedCommit,
|
||||
},
|
||||
}, menuItems[1:]...,
|
||||
}, options[1:]...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), menuItems, createMenuOptions{showCancel: true})
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].function()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) getPatchCommitIndex() int {
|
||||
@@ -62,7 +67,7 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||
}
|
||||
|
||||
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
|
||||
if gui.State.MainContext == "patch-building" {
|
||||
if gui.State.Context == "patch-building" {
|
||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetBranchListDisplayStrings(branches []*commands.Branch, fullDescription bool) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
lines[i] = getBranchDisplayStrings(branches[i], fullDescription)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getBranchDisplayStrings returns the display string of branch
|
||||
func getBranchDisplayStrings(b *commands.Branch, fullDescription bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
if b.Pushables != "" && b.Pullables != "" && b.Pushables != "?" && b.Pullables != "?" {
|
||||
trackColor := color.FgYellow
|
||||
if b.Pushables == "0" && b.Pullables == "0" {
|
||||
trackColor = color.FgGreen
|
||||
}
|
||||
track := utils.ColoredString(fmt.Sprintf("↑%s↓%s", b.Pushables, b.Pullables), trackColor)
|
||||
displayName = fmt.Sprintf("%s %s", displayName, track)
|
||||
}
|
||||
|
||||
recencyColor := color.FgCyan
|
||||
if b.Recency == " *" {
|
||||
recencyColor = color.FgGreen
|
||||
}
|
||||
|
||||
if fullDescription {
|
||||
return []string{utils.ColoredString(b.Recency, recencyColor), displayName, utils.ColoredString(b.UpstreamName, color.FgYellow)}
|
||||
}
|
||||
|
||||
return []string{utils.ColoredString(b.Recency, recencyColor), displayName}
|
||||
}
|
||||
|
||||
// GetBranchColor branch color
|
||||
func GetBranchColor(name string) color.Attribute {
|
||||
branchType := strings.Split(name, "/")[0]
|
||||
|
||||
switch branchType {
|
||||
case "feature":
|
||||
return color.FgGreen
|
||||
case "bugfix":
|
||||
return color.FgYellow
|
||||
case "hotfix":
|
||||
return color.FgRed
|
||||
default:
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
func GetCommitFileListDisplayStrings(branches []*commands.CommitFile) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
lines[i] = getCommitFileDisplayStrings(branches[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getCommitFileDisplayStrings returns the display string of branch
|
||||
func getCommitFileDisplayStrings(f *commands.CommitFile) []string {
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
|
||||
var colour *color.Color
|
||||
switch f.Status {
|
||||
case commands.UNSELECTED:
|
||||
colour = defaultColor
|
||||
case commands.WHOLE:
|
||||
colour = green
|
||||
case commands.PART:
|
||||
colour = yellow
|
||||
}
|
||||
return []string{colour.Sprint(f.DisplayString)}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool) [][]string {
|
||||
lines := make([][]string, len(commits))
|
||||
|
||||
var displayFunc func(*commands.Commit) []string
|
||||
if fullDescription {
|
||||
displayFunc = getFullDescriptionDisplayStringsForCommit
|
||||
} else {
|
||||
displayFunc = getDisplayStringsForCommit
|
||||
}
|
||||
|
||||
for i := range commits {
|
||||
lines[i] = displayFunc(commits[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
tagString := ""
|
||||
truncatedDate := utils.TruncateWithEllipsis(c.Date, 15)
|
||||
secondColumnString := blue.Sprint(truncatedDate)
|
||||
if c.Action != "" {
|
||||
secondColumnString = cyan.Sprint(c.Action)
|
||||
} else if c.ExtraInfo != "" {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(c.ExtraInfo, tagColor) + " "
|
||||
}
|
||||
|
||||
truncatedAuthor := utils.TruncateWithEllipsis(c.Author, 17)
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
func getDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "reflog":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
tagString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
} else if len(c.Tags) > 0 {
|
||||
tagColor := color.New(color.FgMagenta, color.Bold)
|
||||
tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetFileListDisplayStrings(files []*commands.File) [][]string {
|
||||
lines := make([][]string, len(files))
|
||||
|
||||
for i := range files {
|
||||
lines[i] = getFileDisplayStrings(files[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getFileDisplayStrings returns the display string of branch
|
||||
func getFileDisplayStrings(f *commands.File) []string {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !f.Tracked && !f.HasStagedChanges {
|
||||
return []string{red.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
output := green.Sprint(f.DisplayString[0:1])
|
||||
output += red.Sprint(f.DisplayString[1:3])
|
||||
if f.HasUnstagedChanges {
|
||||
output += red.Sprint(f.Name)
|
||||
} else {
|
||||
output += green.Sprint(f.Name)
|
||||
}
|
||||
return []string{output}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetRemoteBranchListDisplayStrings(branches []*commands.RemoteBranch) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
lines[i] = getRemoteBranchDisplayStrings(branches[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getRemoteBranchDisplayStrings returns the display string of branch
|
||||
func getRemoteBranchDisplayStrings(b *commands.RemoteBranch) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
|
||||
return []string{displayName}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetRemoteListDisplayStrings(remotes []*commands.Remote) [][]string {
|
||||
lines := make([][]string, len(remotes))
|
||||
|
||||
for i := range remotes {
|
||||
lines[i] = getRemoteDisplayStrings(remotes[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getRemoteDisplayStrings returns the display string of branch
|
||||
func getRemoteDisplayStrings(r *commands.Remote) []string {
|
||||
return []string{r.Name}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetStashEntryListDisplayStrings(stashEntries []*commands.StashEntry) [][]string {
|
||||
lines := make([][]string, len(stashEntries))
|
||||
|
||||
for i := range stashEntries {
|
||||
lines[i] = getStashEntryDisplayStrings(stashEntries[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getStashEntryDisplayStrings returns the display string of branch
|
||||
func getStashEntryDisplayStrings(s *commands.StashEntry) []string {
|
||||
return []string{s.DisplayString}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func GetTagListDisplayStrings(tags []*commands.Tag) [][]string {
|
||||
lines := make([][]string, len(tags))
|
||||
|
||||
for i := range tags {
|
||||
lines[i] = getTagDisplayStrings(tags[i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getTagDisplayStrings returns the display string of branch
|
||||
func getTagDisplayStrings(t *commands.Tag) []string {
|
||||
return []string{t.Name}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/jesseduffield/pty"
|
||||
)
|
||||
|
||||
func (gui *Gui) onResize() error {
|
||||
if gui.State.Ptmx == nil {
|
||||
return nil
|
||||
}
|
||||
mainView := gui.getMainView()
|
||||
width, height := mainView.Size()
|
||||
|
||||
if err := pty.Setsize(gui.State.Ptmx, &pty.Winsize{Cols: uint16(width), Rows: uint16(height)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: handle resizing properly
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Some commands need to output for a terminal to active certain behaviour.
|
||||
// For example, git won't invoke the GIT_PAGER env var unless it thinks it's
|
||||
// talking to a terminal. We typically write cmd outputs straight to a view,
|
||||
// which is just an io.Reader. the pty package lets us wrap a command in a
|
||||
// pseudo-terminal meaning we'll get the behaviour we want from the underlying
|
||||
// command.
|
||||
func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd) error {
|
||||
width, _ := gui.getMainView().Size()
|
||||
pager := gui.GitCommand.GetPager(width)
|
||||
|
||||
if pager == "" {
|
||||
// if we're not using a custom pager we don't need to use a pty
|
||||
return gui.newCmdTask(viewName, cmd)
|
||||
}
|
||||
|
||||
cmd.Env = append(cmd.Env, "GIT_PAGER="+pager)
|
||||
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil // swallowing for now
|
||||
}
|
||||
|
||||
_, height := view.Size()
|
||||
_, oy := view.Origin()
|
||||
|
||||
manager := gui.getManager(view)
|
||||
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Ptmx = ptmx
|
||||
onClose := func() {
|
||||
ptmx.Close()
|
||||
gui.State.Ptmx = nil
|
||||
}
|
||||
|
||||
if err := gui.onResize(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(ptmx, cmd, height+oy+10, onClose)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package gui
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func (gui *Gui) onResize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) newPtyTask(viewName string, cmd *exec.Cmd) error {
|
||||
return gui.newCmdTask(viewName, cmd)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
|
||||
// we will write the current directory to that file on exit so that their
|
||||
// shell can then change to that directory. That means you don't get kicked
|
||||
// back to the directory that you started with.
|
||||
func (gui *Gui) recordCurrentDirectory() error {
|
||||
if os.Getenv("LAZYGIT_NEW_DIR_FILE") == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// determine current directory, set it in LAZYGIT_NEW_DIR_FILE
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.OSCommand.CreateFileWithContent(os.Getenv("LAZYGIT_NEW_DIR_FILE"), dirName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleQuitWithoutChangingDirectory(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.RetainOriginalDir = true
|
||||
return gui.quit(v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleQuit(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.RetainOriginalDir = false
|
||||
return gui.quit(v)
|
||||
}
|
||||
|
||||
func (gui *Gui) quit(v *gocui.View) error {
|
||||
if gui.State.Updating {
|
||||
return gui.createUpdateQuitConfirmation(gui.g, v)
|
||||
}
|
||||
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
|
||||
return gui.createConfirmationPanel(gui.g, v, true, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
@@ -7,23 +7,33 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []string{"continue", "abort"}
|
||||
type option struct {
|
||||
value string
|
||||
}
|
||||
|
||||
if gui.State.WorkingTreeState == "rebasing" {
|
||||
options = append(options, "skip")
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *option) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.value}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*option{
|
||||
{value: "continue"},
|
||||
{value: "abort"},
|
||||
}
|
||||
|
||||
menuItems := make([]*menuItem, len(options))
|
||||
for i, option := range options {
|
||||
// note to self. Never, EVER, close over loop variables in a function
|
||||
option := option
|
||||
menuItems[i] = &menuItem{
|
||||
displayString: option,
|
||||
onPress: func() error {
|
||||
return gui.genericMergeCommand(option)
|
||||
},
|
||||
if gui.State.WorkingTreeState == "rebasing" {
|
||||
options = append(options, &option{value: "skip"})
|
||||
}
|
||||
|
||||
options = append(options, &option{value: "cancel"})
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
command := options[index].value
|
||||
if command == "cancel" {
|
||||
return nil
|
||||
}
|
||||
return gui.genericMergeCommand(command)
|
||||
}
|
||||
|
||||
var title string
|
||||
@@ -33,7 +43,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
|
||||
title = gui.Tr.SLocalize("RebaseOptionsTitle")
|
||||
}
|
||||
|
||||
return gui.createMenu(title, menuItems, createMenuOptions{showCancel: true})
|
||||
return gui.createMenu(title, options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) genericMergeCommand(command string) error {
|
||||
|
||||
@@ -10,34 +10,41 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type recentRepo struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the path from a recent repo.
|
||||
func (r *recentRepo) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgMagenta)
|
||||
base := filepath.Base(r.path)
|
||||
path := yellow.Sprint(r.path)
|
||||
return []string{base, path}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
recentRepoPaths := gui.Config.GetAppState().RecentRepos
|
||||
reposCount := utils.Min(len(recentRepoPaths), 20)
|
||||
yellow := color.New(color.FgMagenta)
|
||||
// we won't show the current repo hence the -1
|
||||
menuItems := make([]*menuItem, reposCount-1)
|
||||
recentRepos := make([]*recentRepo, reposCount-1)
|
||||
for i, path := range recentRepoPaths[1:reposCount] {
|
||||
innerPath := path
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{
|
||||
filepath.Base(innerPath),
|
||||
yellow.Sprint(innerPath),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := os.Chdir(innerPath); err != nil {
|
||||
return err
|
||||
}
|
||||
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.GitCommand = newGitCommand
|
||||
return gui.Errors.ErrSwitchRepo
|
||||
},
|
||||
}
|
||||
recentRepos[i] = &recentRepo{path: path}
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), menuItems, createMenuOptions{showCancel: true})
|
||||
handleMenuPress := func(index int) error {
|
||||
repo := recentRepos[index]
|
||||
if err := os.Chdir(repo.path); err != nil {
|
||||
return err
|
||||
}
|
||||
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.GitCommand = newGitCommand
|
||||
return gui.Errors.ErrSwitchRepo
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, len(recentRepos), handleMenuPress)
|
||||
}
|
||||
|
||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||
)
|
||||
if err := gui.newPtyTask("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))
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
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
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateReflogResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedReflogCommit()
|
||||
|
||||
return gui.createResetMenu(commit.Sha)
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
v.FocusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine)
|
||||
|
||||
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))
|
||||
displayStrings := presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
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)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToRemoteBranchMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedBranch := gui.getSelectedRemoteBranch()
|
||||
if selectedBranch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(fmt.Sprintf("%s/%s", selectedBranch.RemoteName, selectedBranch.Name))
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"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")
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Remotes.SelectedLine)
|
||||
|
||||
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))
|
||||
|
||||
displayStrings := presentation.GetRemoteListDisplayStrings(gui.State.Remotes)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func (gui *Gui) createResetMenu(ref string) error {
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
menuItems := make([]*menuItem, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
innerStrength := strength
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{
|
||||
fmt.Sprintf("%s reset", strength),
|
||||
color.New(color.FgRed).Sprint(
|
||||
fmt.Sprintf("reset --%s %s", strength, ref),
|
||||
),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetToCommit(ref, innerStrength); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
if err := gui.switchCommitsPanelContext("branch-commits"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
gui.State.Panels.ReflogCommits.SelectedLine = 0
|
||||
|
||||
if err := gui.refreshCommits(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshBranches(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleCommitSelect(gui.g, gui.getCommitsView())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), ref), menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.isSearching = true
|
||||
gui.State.Searching.view = v
|
||||
gui.renderString(gui.g, "search", "")
|
||||
if err := gui.switchFocus(gui.g, v, gui.getSearchView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
||||
if err := gui.switchFocus(gui.g, nil, gui.State.Searching.view); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
gui.renderString(
|
||||
gui.g,
|
||||
"search",
|
||||
fmt.Sprintf(
|
||||
"no matches for '%s' %s",
|
||||
gui.State.Searching.searchString,
|
||||
utils.ColoredString(
|
||||
fmt.Sprintf("%s: exit search mode", gui.getKeyDisplay("universal.return")),
|
||||
theme.OptionsFgColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
gui.renderString(
|
||||
gui.g,
|
||||
"search",
|
||||
fmt.Sprintf(
|
||||
"matches for '%s' (%d of %d) %s",
|
||||
gui.State.Searching.searchString,
|
||||
index+1,
|
||||
total,
|
||||
utils.ColoredString(
|
||||
fmt.Sprintf(
|
||||
"%s: next match, %s: previous match, %s: exit search mode",
|
||||
gui.getKeyDisplay("universal.nextMatch"),
|
||||
gui.getKeyDisplay("universal.prevMatch"),
|
||||
gui.getKeyDisplay("universal.return"),
|
||||
),
|
||||
theme.OptionsFgColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onSearchEscape() error {
|
||||
gui.State.Searching.isSearching = false
|
||||
if gui.State.Searching.view != nil {
|
||||
gui.State.Searching.view.ClearSearch()
|
||||
gui.State.Searching.view = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.switchFocus(gui.g, nil, gui.State.Searching.view); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.onSearchEscape()
|
||||
}
|
||||
+16
-26
@@ -12,13 +12,6 @@ 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 {
|
||||
@@ -94,37 +87,26 @@ func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.handleEscapeLineByLinePanel()
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleStagedSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
return gui.applySelection(state.SecondaryFocused)
|
||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelection(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if state.SecondaryFocused {
|
||||
// for backwards compatibility
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
|
||||
if gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to delete the selected lines (git reset)? 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(true)
|
||||
}, nil)
|
||||
} else {
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelection(reverse bool) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if !reverse && state.SecondaryFocused {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||
}
|
||||
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -159,3 +141,11 @@ func (gui *Gui) applySelection(reverse bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDownSecondaryWhileStaging(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
state.SecondaryFocused = !state.SecondaryFocused
|
||||
|
||||
return gui.refreshStagingPanel(false, -1)
|
||||
}
|
||||
|
||||
+53
-28
@@ -1,9 +1,11 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
@@ -32,17 +34,16 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
stashEntry := gui.getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Stash.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
|
||||
)
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
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)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,12 +53,17 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
||||
|
||||
stashView := gui.getStashView()
|
||||
isFocused := gui.g.CurrentView().Name() == "stash"
|
||||
list, err := utils.RenderList(gui.State.StashEntries, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
displayStrings := presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries)
|
||||
gui.renderDisplayStrings(stashView, displayStrings)
|
||||
v := gui.getStashView()
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
|
||||
if err := gui.resetOrigin(stashView); err != nil {
|
||||
if err := gui.resetOrigin(v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -65,6 +71,34 @@ 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 {
|
||||
@@ -95,11 +129,9 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
|
||||
return gui.createErrorPanel(g, errorMessage)
|
||||
}
|
||||
if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshStashEntries(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshStashEntries(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
@@ -107,18 +139,11 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
|
||||
if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
|
||||
}
|
||||
return gui.createPromptPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("StashChanges"), "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createPromptPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := stashFunc(gui.trimmedContent(v)); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshStashEntries(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshStashEntries(g)
|
||||
return gui.refreshFiles()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onStashPanelSearchSelect(selectedLine int) error {
|
||||
gui.State.Panels.Stash.SelectedLine = selectedLine
|
||||
return gui.handleStashEntrySelect(gui.g, gui.getStashView())
|
||||
}
|
||||
|
||||
+11
-21
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -22,20 +21,11 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
// contents end up cleared
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v.Clear()
|
||||
// TODO: base this off of the current branch
|
||||
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
if err := gui.updateWorkTreeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trackColor := color.FgYellow
|
||||
if state.pushables == "0" && state.pullables == "0" {
|
||||
trackColor = color.FgGreen
|
||||
} else if state.pushables == "?" && state.pullables == "?" {
|
||||
trackColor = color.FgRed
|
||||
}
|
||||
|
||||
status := utils.ColoredString(fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables), trackColor)
|
||||
status := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
|
||||
branches := gui.State.Branches
|
||||
|
||||
if gui.State.WorkingTreeState != "normal" {
|
||||
@@ -44,7 +34,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
|
||||
if len(branches) > 0 {
|
||||
branch := branches[0]
|
||||
name := utils.ColoredString(branch.Name, presentation.GetBranchColor(branch.Name))
|
||||
name := utils.ColoredString(branch.Name, branch.GetColor())
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||
}
|
||||
@@ -120,7 +110,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.newStringTask("main", dashboardString)
|
||||
return gui.renderString(g, "main", dashboardString)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -145,14 +135,6 @@ func lazygitTitle() string {
|
||||
}
|
||||
|
||||
func (gui *Gui) updateWorkTreeState() error {
|
||||
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
gui.State.WorkingTreeState = "rebasing"
|
||||
return nil
|
||||
}
|
||||
merging, err := gui.GitCommand.IsInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,6 +143,14 @@ func (gui *Gui) updateWorkTreeState() error {
|
||||
gui.State.WorkingTreeState = "merging"
|
||||
return nil
|
||||
}
|
||||
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
gui.State.WorkingTreeState = "rebasing"
|
||||
return nil
|
||||
}
|
||||
gui.State.WorkingTreeState = "normal"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
)
|
||||
|
||||
// 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")
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Tags.SelectedLine)
|
||||
|
||||
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" {
|
||||
return gui.renderTagsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderTagsWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
||||
displayStrings := presentation.GetTagListDisplayStrings(gui.State.Tags)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
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())
|
||||
}
|
||||
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 nil
|
||||
}, 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())
|
||||
}
|
||||
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 nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetToTagMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
tag := gui.getSelectedTag()
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createResetMenu(tag.Name)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
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)
|
||||
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := manager.NewTask(manager.NewCmdTask(r, cmd, height+oy+10, nil)); 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 {
|
||||
gui.renderString(gui.g, viewName, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
gui.Log.Warn("updating view")
|
||||
return nil
|
||||
})
|
||||
})
|
||||
gui.viewBufferManagerMap[view.Name()] = manager
|
||||
}
|
||||
|
||||
return manager
|
||||
}
|
||||
+3
-1
@@ -47,7 +47,9 @@ func (gui *Gui) startUpdating(newVersion string) {
|
||||
func (gui *Gui) onUpdateFinish(err error) error {
|
||||
gui.State.Updating = false
|
||||
gui.statusManager.removeStatus("updating")
|
||||
gui.renderString(gui.g, "appStatus", "")
|
||||
if err := gui.renderString(gui.g, "appStatus", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, "Update failed: "+err.Error())
|
||||
}
|
||||
|
||||
+80
-58
@@ -5,7 +5,6 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spkg/bom"
|
||||
@@ -102,21 +101,9 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
case "status":
|
||||
return gui.handleStatusSelect(g, v)
|
||||
case "files":
|
||||
return gui.focusAndSelectFile(g, v)
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
case "branches":
|
||||
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)
|
||||
}
|
||||
return gui.handleBranchSelect(g, v)
|
||||
case "commits":
|
||||
return gui.handleCommitSelect(g, v)
|
||||
case "commitFiles":
|
||||
@@ -130,13 +117,11 @@ func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
case "credentials":
|
||||
return gui.handleCredentialsViewFocused(g, v)
|
||||
case "main":
|
||||
if gui.State.MainContext == "merging" {
|
||||
if gui.State.Context == "merging" {
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
v.Highlight = false
|
||||
return nil
|
||||
case "search":
|
||||
return nil
|
||||
default:
|
||||
panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement"))
|
||||
}
|
||||
@@ -218,18 +203,50 @@ func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
return v.SetOrigin(0, 0)
|
||||
}
|
||||
|
||||
// if the cursor down past the last item, move it to the last line
|
||||
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
||||
if cy < 0 || cy > lineCount {
|
||||
return nil
|
||||
}
|
||||
ox, oy := v.Origin()
|
||||
_, height := v.Size()
|
||||
|
||||
ly := height - 1
|
||||
if ly == -1 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
// if line is above origin, move origin and set cursor to zero
|
||||
// if line is below origin + height, move origin and set cursor to max
|
||||
// otherwise set cursor to value - origin
|
||||
if ly > lineCount {
|
||||
_ = v.SetCursor(cx, cy)
|
||||
_ = v.SetOrigin(ox, 0)
|
||||
} else if cy < oy {
|
||||
_ = v.SetCursor(cx, 0)
|
||||
_ = v.SetOrigin(ox, cy)
|
||||
} else if cy > oy+ly {
|
||||
_ = v.SetCursor(cx, ly)
|
||||
_ = v.SetOrigin(ox, cy-ly)
|
||||
} else {
|
||||
_ = v.SetCursor(cx, cy-oy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) cleanString(s string) string {
|
||||
output := string(bom.Clean([]byte(s)))
|
||||
return utils.NormalizeLinefeeds(output)
|
||||
}
|
||||
|
||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) {
|
||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
|
||||
v.Clear()
|
||||
fmt.Fprint(v, gui.cleanString(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderString resets the origin of a view and sets its content
|
||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) {
|
||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v, err := g.View(viewName)
|
||||
if err != nil {
|
||||
@@ -238,12 +255,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) {
|
||||
if err := v.SetOrigin(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.SetCursor(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.setViewContent(gui.g, v, s)
|
||||
return nil
|
||||
return gui.setViewContent(gui.g, v, s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
@@ -256,8 +270,7 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
}
|
||||
|
||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
|
||||
gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
||||
return nil
|
||||
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// TODO: refactor properly
|
||||
@@ -302,16 +315,6 @@ func (gui *Gui) getCommitFilesView() *gocui.View {
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getMenuView() *gocui.View {
|
||||
v, _ := gui.g.View("menu")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getSearchView() *gocui.View {
|
||||
v, _ := gui.g.View("search")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||
return strings.TrimSpace(v.Buffer())
|
||||
}
|
||||
@@ -343,17 +346,35 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) changeSelectedLine(line *int, total int, change int) {
|
||||
// TODO: find out why we're doing this
|
||||
if *line == -1 {
|
||||
return
|
||||
// generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
|
||||
func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error {
|
||||
_, height := v.Size()
|
||||
overScroll := bottomLine - height + 1
|
||||
if overScroll < 0 {
|
||||
overScroll = 0
|
||||
}
|
||||
if *line+change < 0 {
|
||||
*line = 0
|
||||
} else if *line+change >= total {
|
||||
*line = total - 1
|
||||
if err := v.SetOrigin(0, overScroll); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
|
||||
if up {
|
||||
if *line == -1 || *line == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
*line--
|
||||
} else {
|
||||
*line += change
|
||||
if *line == -1 || *line == total-1 {
|
||||
return
|
||||
}
|
||||
|
||||
*line++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,13 +386,18 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) {
|
||||
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
list := utils.RenderDisplayStrings(displayStrings)
|
||||
isFocused := gui.g.CurrentView().Name() == v.Name()
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderPanelOptions() error {
|
||||
@@ -380,13 +406,18 @@ func (gui *Gui) renderPanelOptions() error {
|
||||
case "menu":
|
||||
return gui.renderMenuOptions()
|
||||
case "main":
|
||||
if gui.State.MainContext == "merging" {
|
||||
if gui.State.Context == "merging" {
|
||||
return gui.renderMergeOptions()
|
||||
}
|
||||
}
|
||||
return gui.renderGlobalOptions()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := gui.g.SetCurrentView(v.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) isPopupPanel(viewName string) bool {
|
||||
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
|
||||
}
|
||||
@@ -418,12 +449,3 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
menuItems := []*menuItem{
|
||||
{
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardAllChangesToAllFiles"),
|
||||
red.Sprint("reset --hard HEAD && git clean -fd"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetAndClean(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardAnyUnstagedChanges"),
|
||||
red.Sprint("git checkout -- ."),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.DiscardAnyUnstagedFileChanges(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("discardUntrackedFiles"),
|
||||
red.Sprint("git clean -fd"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.RemoveUntrackedFiles(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("softReset"),
|
||||
red.Sprint("git reset --soft HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
displayStrings: []string{
|
||||
"mixed reset",
|
||||
red.Sprint("git reset --mixed HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
displayStrings: []string{
|
||||
gui.Tr.SLocalize("hardReset"),
|
||||
red.Sprint("git reset --hard HEAD"),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return gui.createMenu("", menuItems, createMenuOptions{showCancel: true})
|
||||
}
|
||||
+6
-12
@@ -136,6 +136,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Kan commando niet uitvoeren git add --path untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Kan gevolgde bestanden niet negeren",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "Er zijn geen staged bestanden om te commiten",
|
||||
@@ -334,6 +337,9 @@ 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",
|
||||
@@ -751,18 +757,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstream",
|
||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+10
-208
@@ -147,9 +147,6 @@ 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",
|
||||
@@ -162,6 +159,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Cannot git add --patch untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Cannot ignore tracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "There are no staged files to commit",
|
||||
@@ -218,7 +218,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 checked-out branch onto this branch",
|
||||
Other: "rebase branch",
|
||||
}, &i18n.Message{
|
||||
ID: "CantRebaseOntoSelf",
|
||||
Other: "You cannot rebase a branch onto itself",
|
||||
@@ -396,6 +396,9 @@ 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",
|
||||
@@ -501,19 +504,16 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: `select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageSelection",
|
||||
Other: `toggle line staged / unstaged`,
|
||||
Other: `stage selection`,
|
||||
}, &i18n.Message{
|
||||
ID: "ResetSelection",
|
||||
Other: `delete change (git reset)`,
|
||||
Other: `reset selection`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleDragSelect",
|
||||
Other: `toggle drag select`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectHunk",
|
||||
Other: `toggle select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectionForPatch",
|
||||
Other: `add/remove line(s) to patch`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "TogglePanel",
|
||||
@@ -567,54 +567,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Options",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessageTitle",
|
||||
Other: "Commit Message",
|
||||
}, &i18n.Message{
|
||||
ID: "Local-BranchesTitle",
|
||||
Other: "Branches Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "SearchTitle",
|
||||
Other: "Search",
|
||||
}, &i18n.Message{
|
||||
ID: "TagsTitle",
|
||||
Other: "Tags Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Branch-CommitsTitle",
|
||||
Other: "Commits Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "MenuTitle",
|
||||
Other: "Menu",
|
||||
}, &i18n.Message{
|
||||
ID: "RemotesTitle",
|
||||
Other: "Remotes Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsTitle",
|
||||
Other: "Credentials",
|
||||
}, &i18n.Message{
|
||||
ID: "Remote-BranchesTitle",
|
||||
Other: "Remote Branches (in Remotes tab)",
|
||||
}, &i18n.Message{
|
||||
ID: "Patch-BuildingTitle",
|
||||
Other: "Patch Building",
|
||||
}, &i18n.Message{
|
||||
ID: "InformationTitle",
|
||||
Other: "Information",
|
||||
}, &i18n.Message{
|
||||
ID: "SecondaryTitle",
|
||||
Other: "Secondary",
|
||||
}, &i18n.Message{
|
||||
ID: "Reflog-CommitsTitle",
|
||||
Other: "Reflog Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Title",
|
||||
Other: "Title",
|
||||
}, &i18n.Message{
|
||||
ID: "GlobalTitle",
|
||||
Other: "Global Keybindings",
|
||||
}, &i18n.Message{
|
||||
ID: "MerginTitle",
|
||||
Other: "Mergin",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "all merge conflicts resolved. Continue?",
|
||||
@@ -702,12 +654,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll up",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollUpMainPanel",
|
||||
Other: "scroll up main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollDownMainPanel",
|
||||
Other: "scroll down main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Amend Commit",
|
||||
@@ -749,7 +695,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "view commit's files",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit Files",
|
||||
Other: "Commit files",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "go back",
|
||||
@@ -807,9 +753,6 @@ 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`,
|
||||
@@ -879,9 +822,6 @@ 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",
|
||||
@@ -894,144 +834,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstream",
|
||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstreamWithSlash",
|
||||
Other: `Enter upstream as '<remote>/<branchname>'`,
|
||||
}, &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:",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetToUpstreamOptions",
|
||||
Other: "view upstream reset options",
|
||||
}, &i18n.Message{
|
||||
ID: "nextScreenMode",
|
||||
Other: "next screen mode (normal/half/fullscreen)",
|
||||
}, &i18n.Message{
|
||||
ID: "prevScreenMode",
|
||||
Other: "prev screen mode",
|
||||
}, &i18n.Message{
|
||||
ID: "startSearch",
|
||||
Other: "start search",
|
||||
}, &i18n.Message{
|
||||
ID: "Panel",
|
||||
Other: "Panel",
|
||||
}, &i18n.Message{
|
||||
ID: "Keybindings",
|
||||
Other: "Keybindings",
|
||||
}, &i18n.Message{
|
||||
ID: "renameBranch",
|
||||
Other: "rename branch",
|
||||
}, &i18n.Message{
|
||||
ID: "NewBranchNamePrompt",
|
||||
Other: "Enter new branch name for branch",
|
||||
}, &i18n.Message{
|
||||
ID: "RenameBranchWarning",
|
||||
Other: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?",
|
||||
}, &i18n.Message{
|
||||
ID: "openMenu",
|
||||
Other: "open menu",
|
||||
}, &i18n.Message{
|
||||
ID: "closeMenu",
|
||||
Other: "close menu",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+6
-12
@@ -128,6 +128,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CannotGitAdd",
|
||||
Other: "Nie można git add --patch nieśledzonych plików",
|
||||
}, &i18n.Message{
|
||||
ID: "CantIgnoreTrackFiles",
|
||||
Other: "Nie można zignorować nieśledzonych plików",
|
||||
}, &i18n.Message{
|
||||
ID: "NoStagedFilesToCommit",
|
||||
Other: "Brak zatwierdzonych plików do commita",
|
||||
@@ -326,6 +329,9 @@ 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",
|
||||
@@ -734,18 +740,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
}, &i18n.Message{
|
||||
ID: "EnterUpstream",
|
||||
Other: `Enter upstream as '<remote> <branchname>'`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToRemotesList",
|
||||
Other: `return to remotes list`,
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTracked",
|
||||
Other: "Ignore tracked file",
|
||||
}, &i18n.Message{
|
||||
ID: "IgnoreTrackedPrompt",
|
||||
Other: "Are you sure you want to ignore a tracked file?",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"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
|
||||
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(r io.Reader, cmd *exec.Cmd, linesToRead int, onDone func()) func(chan struct{}) error {
|
||||
return func(stop chan struct{}) error {
|
||||
go func() {
|
||||
<-stop
|
||||
if err := commands.Kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
}()
|
||||
|
||||
loadingMutex := sync.Mutex{}
|
||||
|
||||
// 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:
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
_, _ = m.writer.Write([]byte("loading..."))
|
||||
m.refreshView()
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case linesToRead := <-m.readLines:
|
||||
for i := 0; i < linesToRead; i++ {
|
||||
ok := scanner.Scan()
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
loaded = true
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
if !ok {
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
_, _ = m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
}
|
||||
m.refreshView()
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
|
||||
m.refreshView()
|
||||
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
|
||||
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{})
|
||||
|
||||
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()
|
||||
|
||||
m.Log.Infof("done waiting")
|
||||
if taskID < m.newTaskId {
|
||||
m.Log.Infof("returning cos the task is obsolete")
|
||||
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
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package test
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -44,15 +43,3 @@ 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)
|
||||
}
|
||||
|
||||
+30
-72
@@ -20,22 +20,12 @@ var (
|
||||
|
||||
// InactiveBorderColor is the border color of the inactive active frames
|
||||
InactiveBorderColor gocui.Attribute
|
||||
|
||||
// SelectedLineBgColor is the background color for the selected line
|
||||
SelectedLineBgColor color.Attribute
|
||||
|
||||
OptionsFgColor color.Attribute
|
||||
|
||||
OptionsColor gocui.Attribute
|
||||
)
|
||||
|
||||
// UpdateTheme updates all theme variables
|
||||
func UpdateTheme(userConfig *viper.Viper) {
|
||||
ActiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||
InactiveBorderColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||
SelectedLineBgColor = GetBgColor(userConfig.GetStringSlice("gui.theme.selectedLineBgColor"))
|
||||
OptionsColor = GetGocuiColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
OptionsFgColor = GetFgColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
ActiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||
InactiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||
|
||||
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
||||
if isLightTheme {
|
||||
@@ -49,8 +39,8 @@ func UpdateTheme(userConfig *viper.Viper) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func GetGocuiAttribute(key string) gocui.Attribute {
|
||||
// getAttribute gets the gocui color attribute from the string
|
||||
func getAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
@@ -72,75 +62,43 @@ func GetGocuiAttribute(key string) gocui.Attribute {
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// GetFgAttribute gets the color foreground attribute from the string
|
||||
func GetFgAttribute(key string) color.Attribute {
|
||||
colorMap := map[string]color.Attribute{
|
||||
"default": color.FgWhite,
|
||||
"black": color.FgBlack,
|
||||
"red": color.FgRed,
|
||||
"green": color.FgGreen,
|
||||
"yellow": color.FgYellow,
|
||||
"blue": color.FgBlue,
|
||||
"magenta": color.FgMagenta,
|
||||
"cyan": color.FgCyan,
|
||||
"white": color.FgWhite,
|
||||
"bold": color.Bold,
|
||||
"reverse": color.ReverseVideo,
|
||||
"underline": color.Underline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return color.FgWhite
|
||||
}
|
||||
|
||||
// GetBgAttribute gets the color background attribute from the string
|
||||
func GetBgAttribute(key string) color.Attribute {
|
||||
colorMap := map[string]color.Attribute{
|
||||
"default": color.BgWhite,
|
||||
"black": color.BgBlack,
|
||||
"red": color.BgRed,
|
||||
"green": color.BgGreen,
|
||||
"yellow": color.BgYellow,
|
||||
"blue": color.BgBlue,
|
||||
"magenta": color.BgMagenta,
|
||||
"cyan": color.BgCyan,
|
||||
"white": color.BgWhite,
|
||||
"bold": color.Bold,
|
||||
"reverse": color.ReverseVideo,
|
||||
"underline": color.Underline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return color.FgWhite
|
||||
}
|
||||
|
||||
// GetGocuiColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetGocuiColor(keys []string) gocui.Attribute {
|
||||
// getColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func getColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetGocuiAttribute(key)
|
||||
attribute |= getAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetBgColor(keys []string) color.Attribute {
|
||||
var attribute color.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetBgAttribute(key)
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func GetAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
"red": gocui.ColorRed,
|
||||
"green": gocui.ColorGreen,
|
||||
"yellow": gocui.ColorYellow,
|
||||
"blue": gocui.ColorBlue,
|
||||
"magenta": gocui.ColorMagenta,
|
||||
"cyan": gocui.ColorCyan,
|
||||
"white": gocui.ColorWhite,
|
||||
"bold": gocui.AttrBold,
|
||||
"reverse": gocui.AttrReverse,
|
||||
"underline": gocui.AttrUnderline,
|
||||
}
|
||||
return attribute
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetFgColor(keys []string) color.Attribute {
|
||||
var attribute color.Attribute
|
||||
func GetColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetFgAttribute(key)
|
||||
attribute |= GetAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
+15
-26
@@ -3,8 +3,8 @@ package updates
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
|
||||
"github.com/jesseduffield/go-getter"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
@@ -253,34 +254,17 @@ func (u *Updater) update(newVersion string) error {
|
||||
}
|
||||
|
||||
func (u *Updater) downloadAndInstall(rawUrl string) error {
|
||||
url, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := new(getter.HttpGetter)
|
||||
configDir := u.Config.GetUserConfigDir()
|
||||
u.Log.Info("Download directory is " + configDir)
|
||||
|
||||
tempPath := filepath.Join(configDir, "temp_lazygit")
|
||||
u.Log.Info("Temp path to binary is " + tempPath)
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(tempPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(rawUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check server response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("error while trying to download latest lazygit: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
// Get it!
|
||||
if err := g.Get(configDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -291,7 +275,12 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
||||
}
|
||||
u.Log.Info("Binary path is " + binaryPath)
|
||||
|
||||
binaryName := filepath.Base(binaryPath)
|
||||
u.Log.Info("Binary name is " + binaryName)
|
||||
|
||||
// Verify the main file exists
|
||||
tempPath := filepath.Join(configDir, binaryName)
|
||||
u.Log.Info("Temp path to binary is " + tempPath)
|
||||
if _, err := os.Stat(tempPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+57
-74
@@ -6,10 +6,13 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@@ -111,11 +114,51 @@ func Min(x, y int) int {
|
||||
return y
|
||||
}
|
||||
|
||||
func RenderDisplayStrings(displayStringsArr [][]string) string {
|
||||
padWidths := getPadWidths(displayStringsArr)
|
||||
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
|
||||
type Displayable interface {
|
||||
GetDisplayStrings(bool) []string
|
||||
}
|
||||
|
||||
return strings.Join(paddedDisplayStrings, "\n")
|
||||
// RenderList takes a slice of items, confirms they implement the Displayable
|
||||
// interface, then generates a list of their displaystrings to write to a panel's
|
||||
// buffer
|
||||
func RenderList(slice interface{}, isFocused bool) (string, error) {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
return "", errors.New("RenderList given a non-slice type")
|
||||
}
|
||||
|
||||
displayables := make([]Displayable, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
value, ok := s.Index(i).Interface().(Displayable)
|
||||
if !ok {
|
||||
return "", errors.New("item does not implement the Displayable interface")
|
||||
}
|
||||
displayables[i] = value
|
||||
}
|
||||
|
||||
return renderDisplayableList(displayables, isFocused)
|
||||
}
|
||||
|
||||
// renderDisplayableList takes a list of displayable items, obtains their display
|
||||
// strings via GetDisplayStrings() and then returns a single string containing
|
||||
// each item's string representation on its own line, with appropriate horizontal
|
||||
// padding between the item's own strings
|
||||
func renderDisplayableList(items []Displayable, isFocused bool) (string, error) {
|
||||
if len(items) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
stringArrays := getDisplayStringArrays(items, isFocused)
|
||||
|
||||
if !displayArraysAligned(stringArrays) {
|
||||
return "", errors.New("Each item must return the same number of strings to display")
|
||||
}
|
||||
|
||||
padWidths := getPadWidths(stringArrays)
|
||||
paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths)
|
||||
|
||||
return strings.Join(paddedDisplayStrings, "\n"), nil
|
||||
}
|
||||
|
||||
// Decolorise strips a string of color
|
||||
@@ -125,16 +168,10 @@ func Decolorise(str string) string {
|
||||
}
|
||||
|
||||
func getPadWidths(stringArrays [][]string) []int {
|
||||
maxWidth := 0
|
||||
for _, stringArray := range stringArrays {
|
||||
if len(stringArray) > maxWidth {
|
||||
maxWidth = len(stringArray)
|
||||
}
|
||||
}
|
||||
if maxWidth-1 < 0 {
|
||||
if len(stringArrays[0]) <= 1 {
|
||||
return []int{}
|
||||
}
|
||||
padWidths := make([]int, maxWidth-1)
|
||||
padWidths := make([]int, len(stringArrays[0])-1)
|
||||
for i := range padWidths {
|
||||
for _, strings := range stringArrays {
|
||||
uncoloredString := Decolorise(strings[i])
|
||||
@@ -153,14 +190,8 @@ func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string
|
||||
continue
|
||||
}
|
||||
for j, padWidth := range padWidths {
|
||||
if len(stringArray)-1 < j {
|
||||
continue
|
||||
}
|
||||
paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
|
||||
}
|
||||
if len(stringArray)-1 < len(padWidths) {
|
||||
continue
|
||||
}
|
||||
paddedDisplayStrings[i] += stringArray[len(padWidths)]
|
||||
}
|
||||
return paddedDisplayStrings
|
||||
@@ -177,6 +208,14 @@ func displayArraysAligned(stringArrays [][]string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func getDisplayStringArrays(displayables []Displayable, isFocused bool) [][]string {
|
||||
stringArrays := make([][]string, len(displayables))
|
||||
for i, item := range displayables {
|
||||
stringArrays[i] = item.GetDisplayStrings(isFocused)
|
||||
}
|
||||
return stringArrays
|
||||
}
|
||||
|
||||
// IncludesString if the list contains the string
|
||||
func IncludesString(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
@@ -260,59 +299,3 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// NextIntInCycle returns the next int in a slice, returning to the first index if we've reached the end
|
||||
func NextIntInCycle(sl []int, current int) int {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i == len(sl)-1 {
|
||||
return sl[0]
|
||||
}
|
||||
return sl[i+1]
|
||||
}
|
||||
}
|
||||
return sl[0]
|
||||
}
|
||||
|
||||
// PrevIntInCycle returns the prev int in a slice, returning to the first index if we've reached the end
|
||||
func PrevIntInCycle(sl []int, current int) int {
|
||||
for i, val := range sl {
|
||||
if val == current {
|
||||
if i > 0 {
|
||||
return sl[i-1]
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
}
|
||||
return sl[len(sl)-1]
|
||||
}
|
||||
|
||||
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
||||
func TruncateWithEllipsis(str string, limit int) string {
|
||||
if limit == 1 && len(str) > 1 {
|
||||
return "."
|
||||
}
|
||||
|
||||
if limit == 2 && len(str) > 2 {
|
||||
return ".."
|
||||
}
|
||||
|
||||
ellipsis := "..."
|
||||
if len(str) <= limit {
|
||||
return str
|
||||
}
|
||||
|
||||
remainingLength := limit - len(ellipsis)
|
||||
return str[0:remainingLength] + "..."
|
||||
}
|
||||
|
||||
@@ -196,6 +196,166 @@ func TestDisplayArraysAligned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type myDisplayable struct {
|
||||
strings []string
|
||||
}
|
||||
|
||||
type myStruct struct{}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (d *myDisplayable) GetDisplayStrings(isFocused bool) []string {
|
||||
if isFocused {
|
||||
return append(d.strings, "blah")
|
||||
}
|
||||
return d.strings
|
||||
}
|
||||
|
||||
// TestGetDisplayStringArrays is a function.
|
||||
func TestGetDisplayStringArrays(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expected [][]string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
[][]string{{"a", "b"}, {"c", "d"}},
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
true,
|
||||
[][]string{{"a", "b", "blah"}, {"c", "d", "blah"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input, s.isFocused))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderDisplayableList is a function.
|
||||
func TestRenderDisplayableList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
},
|
||||
false,
|
||||
"\n",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"aa", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b", "c"}}),
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"Each item must return the same number of strings to display",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b"}}),
|
||||
},
|
||||
true,
|
||||
"a blah\nb blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := renderDisplayableList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderList is a function.
|
||||
func TestRenderList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input interface{}
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"aa", "b"}},
|
||||
{[]string{"c", "d"}},
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]*myStruct{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"item does not implement the Displayable interface",
|
||||
},
|
||||
{
|
||||
&myStruct{},
|
||||
false,
|
||||
"",
|
||||
"RenderList given a non-slice type",
|
||||
},
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"a"}},
|
||||
},
|
||||
true,
|
||||
"a blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := RenderList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPaddedDisplayStrings is a function.
|
||||
func TestGetPaddedDisplayStrings(t *testing.T) {
|
||||
type scenario struct {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
@@ -61,85 +60,33 @@ func formatTitle(title string) string {
|
||||
|
||||
func formatBinding(binding *gui.Binding) string {
|
||||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
|
||||
}
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
|
||||
}
|
||||
|
||||
func getBindingSections(mApp *app.App) []*bindingSection {
|
||||
bindingSections := []*bindingSection{}
|
||||
|
||||
bindings := mApp.Gui.GetInitialKeybindings()
|
||||
|
||||
type contextAndViewType struct {
|
||||
context string
|
||||
viewName string
|
||||
}
|
||||
|
||||
contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
|
||||
|
||||
for _, binding := range bindings {
|
||||
contexts := []string{}
|
||||
if len(binding.Contexts) == 0 {
|
||||
contexts = append(contexts, "")
|
||||
} else {
|
||||
contexts = append(contexts, binding.Contexts...)
|
||||
// TODO: add context-based keybindings
|
||||
for _, binding := range mApp.Gui.GetInitialKeybindings() {
|
||||
if binding.Description == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
key := contextAndViewType{context: context, viewName: binding.ViewName}
|
||||
existing := contextAndViewBindingMap[key]
|
||||
if existing == nil {
|
||||
contextAndViewBindingMap[key] = []*gui.Binding{binding}
|
||||
} else {
|
||||
contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type groupedBindingsType struct {
|
||||
contextAndView contextAndViewType
|
||||
bindings []*gui.Binding
|
||||
}
|
||||
|
||||
groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap))
|
||||
|
||||
for contextAndView, contextBindings := range contextAndViewBindingMap {
|
||||
groupedBindings = append(groupedBindings, groupedBindingsType{contextAndView: contextAndView, bindings: contextBindings})
|
||||
}
|
||||
|
||||
sort.Slice(groupedBindings, func(i, j int) bool {
|
||||
first := groupedBindings[i].contextAndView
|
||||
second := groupedBindings[j].contextAndView
|
||||
if first.viewName == "" {
|
||||
return true
|
||||
}
|
||||
if second.viewName == "" {
|
||||
return false
|
||||
}
|
||||
return first.viewName < second.viewName || (first.viewName == second.viewName && first.context < second.context)
|
||||
})
|
||||
|
||||
for _, group := range groupedBindings {
|
||||
contextAndView := group.contextAndView
|
||||
contextBindings := group.bindings
|
||||
mApp.Log.Warn("viewname: " + contextAndView.viewName + ", context: " + contextAndView.context)
|
||||
viewName := contextAndView.viewName
|
||||
viewName := binding.ViewName
|
||||
if viewName == "" {
|
||||
viewName = "global"
|
||||
}
|
||||
translatedView := localisedTitle(mApp, viewName)
|
||||
var title string
|
||||
if contextAndView.context == "" {
|
||||
addendum := " " + mApp.Tr.SLocalize("Panel")
|
||||
if viewName == "global" {
|
||||
addendum = ""
|
||||
}
|
||||
title = fmt.Sprintf("%s%s", translatedView, addendum)
|
||||
} else {
|
||||
translatedContextName := localisedTitle(mApp, contextAndView.context)
|
||||
title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.SLocalize("Panel"), translatedContextName)
|
||||
}
|
||||
title := localisedTitle(mApp, viewName)
|
||||
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
}
|
||||
|
||||
for contextName, contextBindings := range mApp.Gui.GetContextMap() {
|
||||
translatedView := localisedTitle(mApp, contextBindings[0].ViewName)
|
||||
translatedContextName := localisedTitle(mApp, contextName)
|
||||
title := fmt.Sprintf("%s (%s)", translatedView, translatedContextName)
|
||||
|
||||
for _, binding := range contextBindings {
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
@@ -170,7 +117,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
|
||||
}
|
||||
|
||||
func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("Keybindings"))
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu"))
|
||||
|
||||
for _, section := range bindingSections {
|
||||
content += formatTitle(section.title)
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
AWS SDK for Go
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2014-2015 Stripe, Inc.
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
// Package awserr represents API error interface accessors for the SDK.
|
||||
package awserr
|
||||
|
||||
// An Error wraps lower level errors with code, message and an original error.
|
||||
// The underlying concrete error type may also satisfy other interfaces which
|
||||
// can be to used to obtain more specific information about the error.
|
||||
//
|
||||
// Calling Error() or String() will always include the full information about
|
||||
// an error based on its underlying type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if awsErr, ok := err.(awserr.Error); ok {
|
||||
// // Get error details
|
||||
// log.Println("Error:", awsErr.Code(), awsErr.Message())
|
||||
//
|
||||
// // Prints out full error message, including original error if there was one.
|
||||
// log.Println("Error:", awsErr.Error())
|
||||
//
|
||||
// // Get original error
|
||||
// if origErr := awsErr.OrigErr(); origErr != nil {
|
||||
// // operate on original error.
|
||||
// }
|
||||
// } else {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
type Error interface {
|
||||
// Satisfy the generic error interface.
|
||||
error
|
||||
|
||||
// Returns the short phrase depicting the classification of the error.
|
||||
Code() string
|
||||
|
||||
// Returns the error details message.
|
||||
Message() string
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErr() error
|
||||
}
|
||||
|
||||
// BatchError is a batch of errors which also wraps lower level errors with
|
||||
// code, message, and original errors. Calling Error() will include all errors
|
||||
// that occurred in the batch.
|
||||
//
|
||||
// Deprecated: Replaced with BatchedErrors. Only defined for backwards
|
||||
// compatibility.
|
||||
type BatchError interface {
|
||||
// Satisfy the generic error interface.
|
||||
error
|
||||
|
||||
// Returns the short phrase depicting the classification of the error.
|
||||
Code() string
|
||||
|
||||
// Returns the error details message.
|
||||
Message() string
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErrs() []error
|
||||
}
|
||||
|
||||
// BatchedErrors is a batch of errors which also wraps lower level errors with
|
||||
// code, message, and original errors. Calling Error() will include all errors
|
||||
// that occurred in the batch.
|
||||
//
|
||||
// Replaces BatchError
|
||||
type BatchedErrors interface {
|
||||
// Satisfy the base Error interface.
|
||||
Error
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErrs() []error
|
||||
}
|
||||
|
||||
// New returns an Error object described by the code, message, and origErr.
|
||||
//
|
||||
// If origErr satisfies the Error interface it will not be wrapped within a new
|
||||
// Error object and will instead be returned.
|
||||
func New(code, message string, origErr error) Error {
|
||||
var errs []error
|
||||
if origErr != nil {
|
||||
errs = append(errs, origErr)
|
||||
}
|
||||
return newBaseError(code, message, errs)
|
||||
}
|
||||
|
||||
// NewBatchError returns an BatchedErrors with a collection of errors as an
|
||||
// array of errors.
|
||||
func NewBatchError(code, message string, errs []error) BatchedErrors {
|
||||
return newBaseError(code, message, errs)
|
||||
}
|
||||
|
||||
// A RequestFailure is an interface to extract request failure information from
|
||||
// an Error such as the request ID of the failed request returned by a service.
|
||||
// RequestFailures may not always have a requestID value if the request failed
|
||||
// prior to reaching the service such as a connection error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if reqerr, ok := err.(RequestFailure); ok {
|
||||
// log.Println("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID())
|
||||
// } else {
|
||||
// log.Println("Error:", err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Combined with awserr.Error:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if awsErr, ok := err.(awserr.Error); ok {
|
||||
// // Generic AWS Error with Code, Message, and original error (if any)
|
||||
// fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
|
||||
//
|
||||
// if reqErr, ok := err.(awserr.RequestFailure); ok {
|
||||
// // A service error occurred
|
||||
// fmt.Println(reqErr.StatusCode(), reqErr.RequestID())
|
||||
// }
|
||||
// } else {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
type RequestFailure interface {
|
||||
Error
|
||||
|
||||
// The status code of the HTTP response.
|
||||
StatusCode() int
|
||||
|
||||
// The request ID returned by the service for a request failure. This will
|
||||
// be empty if no request ID is available such as the request failed due
|
||||
// to a connection error.
|
||||
RequestID() string
|
||||
}
|
||||
|
||||
// NewRequestFailure returns a new request error wrapper for the given Error
|
||||
// provided.
|
||||
func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure {
|
||||
return newRequestError(err, statusCode, reqID)
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
package awserr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SprintError returns a string of the formatted error code.
|
||||
//
|
||||
// Both extra and origErr are optional. If they are included their lines
|
||||
// will be added, but if they are not included their lines will be ignored.
|
||||
func SprintError(code, message, extra string, origErr error) string {
|
||||
msg := fmt.Sprintf("%s: %s", code, message)
|
||||
if extra != "" {
|
||||
msg = fmt.Sprintf("%s\n\t%s", msg, extra)
|
||||
}
|
||||
if origErr != nil {
|
||||
msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// A baseError wraps the code and message which defines an error. It also
|
||||
// can be used to wrap an original error object.
|
||||
//
|
||||
// Should be used as the root for errors satisfying the awserr.Error. Also
|
||||
// for any error which does not fit into a specific error wrapper type.
|
||||
type baseError struct {
|
||||
// Classification of error
|
||||
code string
|
||||
|
||||
// Detailed information about error
|
||||
message string
|
||||
|
||||
// Optional original error this error is based off of. Allows building
|
||||
// chained errors.
|
||||
errs []error
|
||||
}
|
||||
|
||||
// newBaseError returns an error object for the code, message, and errors.
|
||||
//
|
||||
// code is a short no whitespace phrase depicting the classification of
|
||||
// the error that is being created.
|
||||
//
|
||||
// message is the free flow string containing detailed information about the
|
||||
// error.
|
||||
//
|
||||
// origErrs is the error objects which will be nested under the new errors to
|
||||
// be returned.
|
||||
func newBaseError(code, message string, origErrs []error) *baseError {
|
||||
b := &baseError{
|
||||
code: code,
|
||||
message: message,
|
||||
errs: origErrs,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
//
|
||||
// See ErrorWithExtra for formatting.
|
||||
//
|
||||
// Satisfies the error interface.
|
||||
func (b baseError) Error() string {
|
||||
size := len(b.errs)
|
||||
if size > 0 {
|
||||
return SprintError(b.code, b.message, "", errorList(b.errs))
|
||||
}
|
||||
|
||||
return SprintError(b.code, b.message, "", nil)
|
||||
}
|
||||
|
||||
// String returns the string representation of the error.
|
||||
// Alias for Error to satisfy the stringer interface.
|
||||
func (b baseError) String() string {
|
||||
return b.Error()
|
||||
}
|
||||
|
||||
// Code returns the short phrase depicting the classification of the error.
|
||||
func (b baseError) Code() string {
|
||||
return b.code
|
||||
}
|
||||
|
||||
// Message returns the error details message.
|
||||
func (b baseError) Message() string {
|
||||
return b.message
|
||||
}
|
||||
|
||||
// OrigErr returns the original error if one was set. Nil is returned if no
|
||||
// error was set. This only returns the first element in the list. If the full
|
||||
// list is needed, use BatchedErrors.
|
||||
func (b baseError) OrigErr() error {
|
||||
switch len(b.errs) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return b.errs[0]
|
||||
default:
|
||||
if err, ok := b.errs[0].(Error); ok {
|
||||
return NewBatchError(err.Code(), err.Message(), b.errs[1:])
|
||||
}
|
||||
return NewBatchError("BatchedErrors",
|
||||
"multiple errors occurred", b.errs)
|
||||
}
|
||||
}
|
||||
|
||||
// OrigErrs returns the original errors if one was set. An empty slice is
|
||||
// returned if no error was set.
|
||||
func (b baseError) OrigErrs() []error {
|
||||
return b.errs
|
||||
}
|
||||
|
||||
// So that the Error interface type can be included as an anonymous field
|
||||
// in the requestError struct and not conflict with the error.Error() method.
|
||||
type awsError Error
|
||||
|
||||
// A requestError wraps a request or service error.
|
||||
//
|
||||
// Composed of baseError for code, message, and original error.
|
||||
type requestError struct {
|
||||
awsError
|
||||
statusCode int
|
||||
requestID string
|
||||
}
|
||||
|
||||
// newRequestError returns a wrapped error with additional information for
|
||||
// request status code, and service requestID.
|
||||
//
|
||||
// Should be used to wrap all request which involve service requests. Even if
|
||||
// the request failed without a service response, but had an HTTP status code
|
||||
// that may be meaningful.
|
||||
//
|
||||
// Also wraps original errors via the baseError.
|
||||
func newRequestError(err Error, statusCode int, requestID string) *requestError {
|
||||
return &requestError{
|
||||
awsError: err,
|
||||
statusCode: statusCode,
|
||||
requestID: requestID,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
// Satisfies the error interface.
|
||||
func (r requestError) Error() string {
|
||||
extra := fmt.Sprintf("status code: %d, request id: %s",
|
||||
r.statusCode, r.requestID)
|
||||
return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
|
||||
}
|
||||
|
||||
// String returns the string representation of the error.
|
||||
// Alias for Error to satisfy the stringer interface.
|
||||
func (r requestError) String() string {
|
||||
return r.Error()
|
||||
}
|
||||
|
||||
// StatusCode returns the wrapped status code for the error
|
||||
func (r requestError) StatusCode() int {
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
// RequestID returns the wrapped requestID
|
||||
func (r requestError) RequestID() string {
|
||||
return r.requestID
|
||||
}
|
||||
|
||||
// OrigErrs returns the original errors if one was set. An empty slice is
|
||||
// returned if no error was set.
|
||||
func (r requestError) OrigErrs() []error {
|
||||
if b, ok := r.awsError.(BatchedErrors); ok {
|
||||
return b.OrigErrs()
|
||||
}
|
||||
return []error{r.OrigErr()}
|
||||
}
|
||||
|
||||
// An error list that satisfies the golang interface
|
||||
type errorList []error
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
//
|
||||
// Satisfies the error interface.
|
||||
func (e errorList) Error() string {
|
||||
msg := ""
|
||||
// How do we want to handle the array size being zero
|
||||
if size := len(e); size > 0 {
|
||||
for i := 0; i < size; i++ {
|
||||
msg += fmt.Sprintf("%s", e[i].Error())
|
||||
// We check the next index to see if it is within the slice.
|
||||
// If it is, then we append a newline. We do this, because unit tests
|
||||
// could be broken with the additional '\n'
|
||||
if i+1 < size {
|
||||
msg += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Copy deeply copies a src structure to dst. Useful for copying request and
|
||||
// response structures.
|
||||
//
|
||||
// Can copy between structs of different type, but will only copy fields which
|
||||
// are assignable, and exist in both structs. Fields which are not assignable,
|
||||
// or do not exist in both structs are ignored.
|
||||
func Copy(dst, src interface{}) {
|
||||
dstval := reflect.ValueOf(dst)
|
||||
if !dstval.IsValid() {
|
||||
panic("Copy dst cannot be nil")
|
||||
}
|
||||
|
||||
rcopy(dstval, reflect.ValueOf(src), true)
|
||||
}
|
||||
|
||||
// CopyOf returns a copy of src while also allocating the memory for dst.
|
||||
// src must be a pointer type or this operation will fail.
|
||||
func CopyOf(src interface{}) (dst interface{}) {
|
||||
dsti := reflect.New(reflect.TypeOf(src).Elem())
|
||||
dst = dsti.Interface()
|
||||
rcopy(dsti, reflect.ValueOf(src), true)
|
||||
return
|
||||
}
|
||||
|
||||
// rcopy performs a recursive copy of values from the source to destination.
|
||||
//
|
||||
// root is used to skip certain aspects of the copy which are not valid
|
||||
// for the root node of a object.
|
||||
func rcopy(dst, src reflect.Value, root bool) {
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch src.Kind() {
|
||||
case reflect.Ptr:
|
||||
if _, ok := src.Interface().(io.Reader); ok {
|
||||
if dst.Kind() == reflect.Ptr && dst.Elem().CanSet() {
|
||||
dst.Elem().Set(src)
|
||||
} else if dst.CanSet() {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else {
|
||||
e := src.Type().Elem()
|
||||
if dst.CanSet() && !src.IsNil() {
|
||||
if _, ok := src.Interface().(*time.Time); !ok {
|
||||
dst.Set(reflect.New(e))
|
||||
} else {
|
||||
tempValue := reflect.New(e)
|
||||
tempValue.Elem().Set(src.Elem())
|
||||
// Sets time.Time's unexported values
|
||||
dst.Set(tempValue)
|
||||
}
|
||||
}
|
||||
if src.Elem().IsValid() {
|
||||
// Keep the current root state since the depth hasn't changed
|
||||
rcopy(dst.Elem(), src.Elem(), root)
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
t := dst.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
name := t.Field(i).Name
|
||||
srcVal := src.FieldByName(name)
|
||||
dstVal := dst.FieldByName(name)
|
||||
if srcVal.IsValid() && dstVal.CanSet() {
|
||||
rcopy(dstVal, srcVal, false)
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if src.IsNil() {
|
||||
break
|
||||
}
|
||||
|
||||
s := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
dst.Set(s)
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
rcopy(dst.Index(i), src.Index(i), false)
|
||||
}
|
||||
case reflect.Map:
|
||||
if src.IsNil() {
|
||||
break
|
||||
}
|
||||
|
||||
s := reflect.MakeMap(src.Type())
|
||||
dst.Set(s)
|
||||
for _, k := range src.MapKeys() {
|
||||
v := src.MapIndex(k)
|
||||
v2 := reflect.New(v.Type()).Elem()
|
||||
rcopy(v2, v, false)
|
||||
dst.SetMapIndex(k, v2)
|
||||
}
|
||||
default:
|
||||
// Assign the value if possible. If its not assignable, the value would
|
||||
// need to be converted and the impact of that may be unexpected, or is
|
||||
// not compatible with the dst type.
|
||||
if src.Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// DeepEqual returns if the two values are deeply equal like reflect.DeepEqual.
|
||||
// In addition to this, this method will also dereference the input values if
|
||||
// possible so the DeepEqual performed will not fail if one parameter is a
|
||||
// pointer and the other is not.
|
||||
//
|
||||
// DeepEqual will not perform indirection of nested values of the input parameters.
|
||||
func DeepEqual(a, b interface{}) bool {
|
||||
ra := reflect.Indirect(reflect.ValueOf(a))
|
||||
rb := reflect.Indirect(reflect.ValueOf(b))
|
||||
|
||||
if raValid, rbValid := ra.IsValid(), rb.IsValid(); !raValid && !rbValid {
|
||||
// If the elements are both nil, and of the same type the are equal
|
||||
// If they are of different types they are not equal
|
||||
return reflect.TypeOf(a) == reflect.TypeOf(b)
|
||||
} else if raValid != rbValid {
|
||||
// Both values must be valid to be equal
|
||||
return false
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(ra.Interface(), rb.Interface())
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
|
||||
|
||||
// rValuesAtPath returns a slice of values found in value v. The values
|
||||
// in v are explored recursively so all nested values are collected.
|
||||
func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value {
|
||||
pathparts := strings.Split(path, "||")
|
||||
if len(pathparts) > 1 {
|
||||
for _, pathpart := range pathparts {
|
||||
vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm)
|
||||
if len(vals) > 0 {
|
||||
return vals
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
|
||||
components := strings.Split(path, ".")
|
||||
for len(values) > 0 && len(components) > 0 {
|
||||
var index *int64
|
||||
var indexStar bool
|
||||
c := strings.TrimSpace(components[0])
|
||||
if c == "" { // no actual component, illegal syntax
|
||||
return nil
|
||||
} else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
|
||||
// TODO normalize case for user
|
||||
return nil // don't support unexported fields
|
||||
}
|
||||
|
||||
// parse this component
|
||||
if m := indexRe.FindStringSubmatch(c); m != nil {
|
||||
c = m[1]
|
||||
if m[2] == "" {
|
||||
index = nil
|
||||
indexStar = true
|
||||
} else {
|
||||
i, _ := strconv.ParseInt(m[2], 10, 32)
|
||||
index = &i
|
||||
indexStar = false
|
||||
}
|
||||
}
|
||||
|
||||
nextvals := []reflect.Value{}
|
||||
for _, value := range values {
|
||||
// pull component name out of struct member
|
||||
if value.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
if c == "*" { // pull all members
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
if f := reflect.Indirect(value.Field(i)); f.IsValid() {
|
||||
nextvals = append(nextvals, f)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value = value.FieldByNameFunc(func(name string) bool {
|
||||
if c == name {
|
||||
return true
|
||||
} else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 {
|
||||
if !value.IsNil() {
|
||||
value.Set(reflect.Zero(value.Type()))
|
||||
}
|
||||
return []reflect.Value{value}
|
||||
}
|
||||
|
||||
if createPath && value.Kind() == reflect.Ptr && value.IsNil() {
|
||||
// TODO if the value is the terminus it should not be created
|
||||
// if the value to be set to its position is nil.
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
value = value.Elem()
|
||||
} else {
|
||||
value = reflect.Indirect(value)
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
|
||||
if !createPath && value.IsNil() {
|
||||
value = reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
|
||||
if indexStar || index != nil {
|
||||
nextvals = []reflect.Value{}
|
||||
for _, valItem := range values {
|
||||
value := reflect.Indirect(valItem)
|
||||
if value.Kind() != reflect.Slice {
|
||||
continue
|
||||
}
|
||||
|
||||
if indexStar { // grab all indices
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
idx := reflect.Indirect(value.Index(i))
|
||||
if idx.IsValid() {
|
||||
nextvals = append(nextvals, idx)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// pull out index
|
||||
i := int(*index)
|
||||
if i >= value.Len() { // check out of bounds
|
||||
if createPath {
|
||||
// TODO resize slice
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if i < 0 { // support negative indexing
|
||||
i = value.Len() + i
|
||||
}
|
||||
value = reflect.Indirect(value.Index(i))
|
||||
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
|
||||
if !createPath && value.IsNil() {
|
||||
value = reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
}
|
||||
|
||||
components = components[1:]
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ValuesAtPath returns a list of values at the case insensitive lexical
|
||||
// path inside of a structure.
|
||||
func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
|
||||
result, err := jmespath.Search(path, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(result)
|
||||
if !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
||||
return nil, nil
|
||||
}
|
||||
if s, ok := result.([]interface{}); ok {
|
||||
return s, err
|
||||
}
|
||||
if v.Kind() == reflect.Map && v.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if v.Kind() == reflect.Slice {
|
||||
out := make([]interface{}, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
out[i] = v.Index(i).Interface()
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
return []interface{}{result}, nil
|
||||
}
|
||||
|
||||
// SetValueAtPath sets a value at the case insensitive lexical path inside
|
||||
// of a structure.
|
||||
func SetValueAtPath(i interface{}, path string, v interface{}) {
|
||||
if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil {
|
||||
for _, rval := range rvals {
|
||||
if rval.Kind() == reflect.Ptr && rval.IsNil() {
|
||||
continue
|
||||
}
|
||||
setValue(rval, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setValue(dstVal reflect.Value, src interface{}) {
|
||||
if dstVal.Kind() == reflect.Ptr {
|
||||
dstVal = reflect.Indirect(dstVal)
|
||||
}
|
||||
srcVal := reflect.ValueOf(src)
|
||||
|
||||
if !srcVal.IsValid() { // src is literal nil
|
||||
if dstVal.CanAddr() {
|
||||
// Convert to pointer so that pointer's value can be nil'ed
|
||||
// dstVal = dstVal.Addr()
|
||||
}
|
||||
dstVal.Set(reflect.Zero(dstVal.Type()))
|
||||
|
||||
} else if srcVal.Kind() == reflect.Ptr {
|
||||
if srcVal.IsNil() {
|
||||
srcVal = reflect.Zero(dstVal.Type())
|
||||
} else {
|
||||
srcVal = reflect.ValueOf(src).Elem()
|
||||
}
|
||||
dstVal.Set(srcVal)
|
||||
} else {
|
||||
dstVal.Set(srcVal)
|
||||
}
|
||||
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Prettify returns the string representation of a value.
|
||||
func Prettify(i interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
prettify(reflect.ValueOf(i), 0, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// prettify will recursively walk value v to build a textual
|
||||
// representation of the value.
|
||||
func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
strtype := v.Type().String()
|
||||
if strtype == "time.Time" {
|
||||
fmt.Fprintf(buf, "%s", v.Interface())
|
||||
break
|
||||
} else if strings.HasPrefix(strtype, "io.") {
|
||||
buf.WriteString("<buffer>")
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString("{\n")
|
||||
|
||||
names := []string{}
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
name := v.Type().Field(i).Name
|
||||
f := v.Field(i)
|
||||
if name[0:1] == strings.ToLower(name[0:1]) {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice || f.Kind() == reflect.Map) && f.IsNil() {
|
||||
continue // ignore unset fields
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
val := v.FieldByName(n)
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(n + ": ")
|
||||
prettify(val, indent+2, buf)
|
||||
|
||||
if i < len(names)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
case reflect.Slice:
|
||||
strtype := v.Type().String()
|
||||
if strtype == "[]uint8" {
|
||||
fmt.Fprintf(buf, "<binary> len %d", v.Len())
|
||||
break
|
||||
}
|
||||
|
||||
nl, id, id2 := "", "", ""
|
||||
if v.Len() > 3 {
|
||||
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
|
||||
}
|
||||
buf.WriteString("[" + nl)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
buf.WriteString(id2)
|
||||
prettify(v.Index(i), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString("," + nl)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(nl + id + "]")
|
||||
case reflect.Map:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i, k := range v.MapKeys() {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(k.String() + ": ")
|
||||
prettify(v.MapIndex(k), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
default:
|
||||
if !v.IsValid() {
|
||||
fmt.Fprint(buf, "<invalid value>")
|
||||
return
|
||||
}
|
||||
format := "%v"
|
||||
switch v.Interface().(type) {
|
||||
case string:
|
||||
format = "%q"
|
||||
case io.ReadSeeker, io.Reader:
|
||||
format = "buffer(%p)"
|
||||
}
|
||||
fmt.Fprintf(buf, format, v.Interface())
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringValue returns the string representation of a value.
|
||||
func StringValue(i interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
stringValue(reflect.ValueOf(i), 0, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
names := []string{}
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
name := v.Type().Field(i).Name
|
||||
f := v.Field(i)
|
||||
if name[0:1] == strings.ToLower(name[0:1]) {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() {
|
||||
continue // ignore unset fields
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
val := v.FieldByName(n)
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(n + ": ")
|
||||
stringValue(val, indent+2, buf)
|
||||
|
||||
if i < len(names)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
case reflect.Slice:
|
||||
nl, id, id2 := "", "", ""
|
||||
if v.Len() > 3 {
|
||||
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
|
||||
}
|
||||
buf.WriteString("[" + nl)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
buf.WriteString(id2)
|
||||
stringValue(v.Index(i), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString("," + nl)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(nl + id + "]")
|
||||
case reflect.Map:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i, k := range v.MapKeys() {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(k.String() + ": ")
|
||||
stringValue(v.MapIndex(k), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
default:
|
||||
format := "%v"
|
||||
switch v.Interface().(type) {
|
||||
case string:
|
||||
format = "%q"
|
||||
}
|
||||
fmt.Fprintf(buf, format, v.Interface())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user