mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 18:26:03 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95b147079f | ||
|
|
83757f1065 | ||
|
|
f2036b42e5 | ||
|
|
21b7d41845 | ||
|
|
91a404d033 | ||
|
|
d027cf969c | ||
|
|
c7f68a2ef9 | ||
|
|
78e55a05c1 | ||
|
|
ca71555d0b | ||
|
|
77fdac01ff | ||
|
|
8301fae01e | ||
|
|
e9161ad702 | ||
|
|
a0a139da1f | ||
|
|
8f13d1da91 | ||
|
|
d5fe9ce2c7 | ||
|
|
37acc17cf3 | ||
|
|
569ec5919c | ||
|
|
19719becf5 | ||
|
|
e64057b803 | ||
|
|
672667aa3e | ||
|
|
8a06b6067e | ||
|
|
2dcc52abd0 | ||
|
|
c831ad39c9 | ||
|
|
0cf78ea9ad | ||
|
|
3d51fbf354 | ||
|
|
e7a2c7cc3e | ||
|
|
708a078412 | ||
|
|
bbcc4b7b70 | ||
|
|
45bba0a3c5 | ||
|
|
d105e2690a | ||
|
|
32d3e497c3 | ||
|
|
30a5d1b486 | ||
|
|
6b3ea56add | ||
|
|
c3aefdb98e | ||
|
|
094939451d | ||
|
|
0e23f44b84 | ||
|
|
daecdd7c2b | ||
|
|
7c8df28d01 | ||
|
|
65917272a2 | ||
|
|
137fd80fdb | ||
|
|
98fbc61221 | ||
|
|
f80d15062b | ||
|
|
b1b0219f04 | ||
|
|
b1941c33f7 | ||
|
|
a15a7b607d | ||
|
|
d50283f5ee | ||
|
|
6508d3b872 | ||
|
|
65b8cef1b8 | ||
|
|
5d460e1e5e | ||
|
|
3d3e0be7bd | ||
|
|
c06c0b7133 | ||
|
|
91f6630907 | ||
|
|
60085cf679 | ||
|
|
389480b8fc | ||
|
|
b5c4f78e9d | ||
|
|
59b0e2d70a | ||
|
|
39bd1a4628 | ||
|
|
1c1445c896 | ||
|
|
1e8ade2431 | ||
|
|
a990fbc3eb | ||
|
|
e5574e7fe5 | ||
|
|
6c8a924fad | ||
|
|
64706257ca | ||
|
|
6183d92315 | ||
|
|
31823a7405 | ||
|
|
85ddd623f6 | ||
|
|
9212dda9c3 | ||
|
|
93d7b37c8d | ||
|
|
8470bcd71d | ||
|
|
3aab37611a | ||
|
|
8fbcc36331 | ||
|
|
dadb646252 | ||
|
|
0227b93409 | ||
|
|
b0ec0821d5 | ||
|
|
13a7806cac | ||
|
|
41c76fb748 | ||
|
|
ac0c3b9f92 | ||
|
|
1be0ff8da7 | ||
|
|
2169b5109f | ||
|
|
4a2292a53c | ||
|
|
7df4b736cf | ||
|
|
e47ad846c4 | ||
|
|
8f68ac2129 | ||
|
|
1ea2825a54 | ||
|
|
19146d61b1 | ||
|
|
e541b809ce | ||
|
|
6ca08c6519 | ||
|
|
b43540820b | ||
|
|
3d57da71eb | ||
|
|
0130fd3666 | ||
|
|
395afc4a8d | ||
|
|
31e201ca52 | ||
|
|
0abd7ad6be | ||
|
|
b3522c48d9 | ||
|
|
0fc58a7986 | ||
|
|
54241d8ab9 | ||
|
|
355f1615ab | ||
|
|
113252b0ae | ||
|
|
1cd7d14029 | ||
|
|
87c2fb6a4a | ||
|
|
9912998bb7 | ||
|
|
e223d3d8de | ||
|
|
ec31fc4cc7 | ||
|
|
3ce2b9b79a | ||
|
|
a79182e50d | ||
|
|
6f4c595dde | ||
|
|
0eb3090ad6 | ||
|
|
6ea25bd259 | ||
|
|
fe5f087f9c | ||
|
|
79299be3b2 | ||
|
|
4c9b620bd0 | ||
|
|
a7508a5dfd | ||
|
|
1a3d765c4c | ||
|
|
4058c71ca0 | ||
|
|
3fc22a6010 | ||
|
|
a9fe0b8000 | ||
|
|
5af7b0235e | ||
|
|
bf946200e9 | ||
|
|
890cc87724 | ||
|
|
8eb0b0f4ca | ||
|
|
e6a8dc0bcf | ||
|
|
02c497fad6 | ||
|
|
d0ab747479 | ||
|
|
f94d0be2c9 | ||
|
|
9fd9fd6816 | ||
|
|
b8717d750a | ||
|
|
8ad01fe32f | ||
|
|
fdb543fa7d |
@@ -1,62 +0,0 @@
|
|||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.13
|
|
||||||
environment:
|
|
||||||
GO111MODULE: "on"
|
|
||||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run:
|
|
||||||
name: Run gofmt -s
|
|
||||||
command: |
|
|
||||||
if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then
|
|
||||||
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- pkg-cache-{{ checksum "go.sum" }}-v5
|
|
||||||
- run:
|
|
||||||
name: Run tests
|
|
||||||
command: |
|
|
||||||
./test.sh
|
|
||||||
- run:
|
|
||||||
name: Push on codecov result
|
|
||||||
command: |
|
|
||||||
bash <(curl -s https://codecov.io/bash)
|
|
||||||
- run:
|
|
||||||
name: Compile project on every platform
|
|
||||||
command: |
|
|
||||||
go get github.com/mitchellh/gox
|
|
||||||
GOFLAGS=-mod=vendor gox -parallel 10 -os "linux freebsd netbsd windows" -osarch "darwin/i386 darwin/amd64"
|
|
||||||
- save_cache:
|
|
||||||
key: pkg-cache-{{ checksum "go.sum" }}-v5
|
|
||||||
paths:
|
|
||||||
- ~/.cache/go-build
|
|
||||||
|
|
||||||
release:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.13
|
|
||||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run:
|
|
||||||
name: Run gorelease
|
|
||||||
command: |
|
|
||||||
curl -sL https://git.io/goreleaser | bash
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
build:
|
|
||||||
jobs:
|
|
||||||
- build
|
|
||||||
release:
|
|
||||||
jobs:
|
|
||||||
- release:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /v[0-9]+(\.[0-9]+)*/
|
|
||||||
branches:
|
|
||||||
ignore: /.*/
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
name: Continuous Delivery
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cd:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Unshallow repo
|
||||||
|
run: git fetch --prune --unshallow
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.14.x
|
||||||
|
- name: Run goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
||||||
|
- name: Get tag
|
||||||
|
id: tag
|
||||||
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
- name: Bump Homebrew
|
||||||
|
uses: dawidd6/action-homebrew-bump-formula@v1
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_API_TOKEN}}
|
||||||
|
formula: lazygit
|
||||||
|
url: "https://github.com/${{github.repository}}/archive/${{steps.tag.outputs.tag}}.tar.gz"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GOFLAGS: -mod=vendor
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.14.x
|
||||||
|
- name: Cache build
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.cache/go-build
|
||||||
|
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}
|
||||||
|
restore-keys: |
|
||||||
|
${{runner.os}}-go-
|
||||||
|
- name: Format code
|
||||||
|
run: |
|
||||||
|
if [ $(find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;|wc -l) -gt 0 ]; then
|
||||||
|
find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \;
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Test code
|
||||||
|
run: |
|
||||||
|
./test.sh
|
||||||
|
- name: Build binaries
|
||||||
|
uses: goreleaser/goreleaser-action@v1
|
||||||
|
with:
|
||||||
|
args: --skip-publish --snapshot
|
||||||
+2
-2
@@ -2,12 +2,12 @@
|
|||||||
# docker build -t lazygit .
|
# docker build -t lazygit .
|
||||||
# docker run -it lazygit:latest /bin/sh -l
|
# docker run -it lazygit:latest /bin/sh -l
|
||||||
|
|
||||||
FROM golang:1.13-alpine3.10
|
FROM golang:1.14-alpine3.11
|
||||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||||
COPY ./ .
|
COPY ./ .
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build
|
RUN CGO_ENABLED=0 GOOS=linux go build
|
||||||
|
|
||||||
FROM alpine:3.10
|
FROM alpine:3.11
|
||||||
RUN apk add -U git xdg-utils
|
RUN apk add -U git xdg-utils
|
||||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||||
COPY --from=0 /go/src/github.com/jesseduffield/lazygit /go/src/github.com/jesseduffield/lazygit
|
COPY --from=0 /go/src/github.com/jesseduffield/lazygit /go/src/github.com/jesseduffield/lazygit
|
||||||
|
|||||||
@@ -1,32 +1,56 @@
|
|||||||
# 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) []()
|
# lazygit
|
||||||
|
|
||||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui 'gocui') library.
|
 [](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)
|
||||||
|
|
||||||
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!*
|
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 to step through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, you have to edit an arcane patch file _by hand_? *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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- [Installation](https://github.com/jesseduffield/lazygit#installation)
|
## Table of contents
|
||||||
- [Usage](https://github.com/jesseduffield/lazygit#usage),
|
|
||||||
[Keybindings](/docs/keybindings)
|
|
||||||
- [Cool Features](https://github.com/jesseduffield/lazygit#cool-features)
|
|
||||||
- [Contributing](https://github.com/jesseduffield/lazygit#contributing)
|
|
||||||
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
|
|
||||||
- [Rebase Magic Video Tutorial](https://youtu.be/4XaToVut_hs)
|
|
||||||
- [Twitch Stream](https://www.twitch.tv/jesseduffield)
|
|
||||||
|
|
||||||
[<img src="https://i.imgur.com/sVEktDn.png">](https://youtu.be/CPLdltN7wgE)
|
- [Installation](#installation)
|
||||||
|
- [Binary releases](#binary-releases)
|
||||||
|
- [Homebrew](#homebrew)
|
||||||
|
- [MacPorts](#macports)
|
||||||
|
- [Ubuntu](#ubuntu)
|
||||||
|
- [Void Linux](#void-linux)
|
||||||
|
- [Scoop (Windows)](<#scoop-(windows)>)
|
||||||
|
- [Arch Linux](#arch-linux)
|
||||||
|
- [Fedora and CentOS 7](#fedora-and-centos-7)
|
||||||
|
- [Conda](#conda)
|
||||||
|
- [Go](#go)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Keybindings](#keybindings)
|
||||||
|
- [Changing directory on exit](#changing-directory-on-exit)
|
||||||
|
- [Undo/Redo](#undo/redo)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Custom pagers](#configuration)
|
||||||
|
- [Tutorials](#tutorials)
|
||||||
|
- [Cool Features](#cool-features)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [Donate](#donate)
|
||||||
|
- [Alternatives](#alternatives)
|
||||||
|
|
||||||
Github Sponsors is matching all donations dollar-for-dollar for 12 months so if you're feeling generous consider [sponsoring me](https://github.com/sponsors/jesseduffield)
|
Github Sponsors is matching all donations dollar-for-dollar for 12 months so if you're feeling generous consider [sponsoring me](https://github.com/sponsors/jesseduffield)
|
||||||
|
|
||||||
|
[<img src="https://i.imgur.com/sVEktDn.png">](https://youtu.be/CPLdltN7wgE)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Binary Releases
|
||||||
|
|
||||||
|
For Windows, Mac OS or Linux, you can download a binary release [here](/releases).
|
||||||
|
|
||||||
### Homebrew
|
### 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.
|
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:
|
Tap:
|
||||||
|
|
||||||
```
|
```
|
||||||
brew install jesseduffield/lazygit/lazygit
|
brew install jesseduffield/lazygit/lazygit
|
||||||
```
|
```
|
||||||
@@ -38,8 +62,10 @@ brew install lazygit
|
|||||||
```
|
```
|
||||||
|
|
||||||
### MacPorts
|
### MacPorts
|
||||||
|
|
||||||
Latest version built from github releases.
|
Latest version built from github releases.
|
||||||
Tap:
|
Tap:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo port install lazygit
|
sudo port install lazygit
|
||||||
```
|
```
|
||||||
@@ -64,6 +90,18 @@ They follow upstream latest releases
|
|||||||
sudo xbps-install -S lazygit
|
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
|
### Arch Linux
|
||||||
|
|
||||||
Packages for Arch Linux are available via AUR (Arch User Repository).
|
Packages for Arch Linux are available via AUR (Arch User Repository).
|
||||||
@@ -71,11 +109,11 @@ 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
|
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.
|
and the git version which builds from the most recent commit.
|
||||||
|
|
||||||
- Stable: https://aur.archlinux.org/packages/lazygit/
|
- Stable: <https://aur.archlinux.org/packages/lazygit/>
|
||||||
- Development: https://aur.archlinux.org/packages/lazygit-git/
|
- Development: <https://aur.archlinux.org/packages/lazygit-git/>
|
||||||
|
|
||||||
Instruction of how to install AUR content can be found here:
|
Instruction of how to install AUR content can be found here:
|
||||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
<https://wiki.archlinux.org/index.php/Arch_User_Repository>
|
||||||
|
|
||||||
### Fedora and CentOS 7
|
### Fedora and CentOS 7
|
||||||
|
|
||||||
@@ -88,16 +126,12 @@ sudo dnf install lazygit
|
|||||||
|
|
||||||
### Conda
|
### 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
|
```sh
|
||||||
conda install -c conda-forge lazygit
|
conda install -c conda-forge lazygit
|
||||||
```
|
```
|
||||||
|
|
||||||
### Binary Release (Windows/Linux/OSX)
|
|
||||||
|
|
||||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases).
|
|
||||||
|
|
||||||
### Go
|
### Go
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -110,20 +144,27 @@ may need to add `~/go/bin` to your \$PATH (MacOS/Linux), or `%HOME%\go\bin`
|
|||||||
(Windows). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries,
|
(Windows). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries,
|
||||||
not apps like Lazygit).
|
not apps like Lazygit).
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Call `lazygit` in your terminal inside a git repository. If you want, you can
|
Call `lazygit` in your terminal inside a git repository.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ lazygit
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, you can
|
||||||
also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or
|
also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or
|
||||||
whichever rc file you're using).
|
whichever rc file you're using).
|
||||||
|
|
||||||
- Basic video tutorial [here](https://youtu.be/VDXvbHZYeKY).
|
### Keybindings
|
||||||
- Rebase Magic tutorial [here](https://youtu.be/4XaToVut_hs)
|
|
||||||
- List of keybindings
|
You can check out the list of keybindings [here](/docs/keybindings).
|
||||||
[here](/docs/keybindings).
|
|
||||||
|
### Changing Directory On Exit
|
||||||
## 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):
|
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()
|
lg()
|
||||||
{
|
{
|
||||||
@@ -137,8 +178,28 @@ lg()
|
|||||||
fi
|
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`.
|
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`.
|
||||||
|
|
||||||
|
### Undo/Redo
|
||||||
|
|
||||||
|
See the [docs](/docs/Undoing.md)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Check out the [configuration docs](docs/Config.md).
|
||||||
|
|
||||||
|
### Custom Pagers
|
||||||
|
|
||||||
|
See the [docs](docs/Custom_Pagers.md)
|
||||||
|
|
||||||
|
## Tutorials
|
||||||
|
|
||||||
|
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
|
||||||
|
- [Rebase Magic Video Tutorial](https://youtu.be/4XaToVut_hs)
|
||||||
|
- [Twitch Stream](https://www.twitch.tv/jesseduffield)
|
||||||
|
|
||||||
|
|
||||||
## Cool features
|
## Cool features
|
||||||
|
|
||||||
- Adding files easily
|
- Adding files easily
|
||||||
@@ -154,14 +215,14 @@ Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch
|
|||||||
|
|
||||||
### Interactive Rebasing
|
### Interactive Rebasing
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
|
We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
|
||||||
For contributor discussion about things not better discussed here in the repo, join the slack channel
|
For contributor discussion about things not better discussed here in the repo, join the slack channel
|
||||||
|
|
||||||
[](https://join.slack.com/t/lazygit/shared_invite/enQtNDE3MjIwNTYyMDA0LTM3Yjk3NzdiYzhhNTA1YjM4Y2M4MWNmNDBkOTI0YTE4YjQ1ZmI2YWRhZTgwNjg2YzhhYjg3NDBlMmQyMTI5N2M)
|
[](https://join.slack.com/t/lazygit/shared_invite/zt-5bo2clzo-hB8ZTVN5dWUCqj5QFiQVLA)
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
|
|||||||
+43
-14
@@ -1,24 +1,25 @@
|
|||||||
# User Config:
|
# User Config
|
||||||
|
|
||||||
Default path for the config file:
|
Default path for the config file:
|
||||||
|
|
||||||
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
|
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
|
||||||
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||||
|
|
||||||
## Default:
|
## Default
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
gui:
|
gui:
|
||||||
# stuff relating to the UI
|
# stuff relating to the UI
|
||||||
scrollHeight: 2 # how many lines you scroll by
|
scrollHeight: 2 # how many lines you scroll by
|
||||||
scrollPastBottom: true # enable scrolling past the bottom
|
scrollPastBottom: true # enable scrolling past the bottom
|
||||||
|
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||||
theme:
|
theme:
|
||||||
lightTheme: false # For terminals with a light background
|
lightTheme: false # For terminals with a light background
|
||||||
activeBorderColor:
|
activeBorderColor:
|
||||||
- white
|
- white
|
||||||
- bold
|
- bold
|
||||||
inactiveBorderColor:
|
inactiveBorderColor:
|
||||||
- white
|
- green
|
||||||
optionsTextColor:
|
optionsTextColor:
|
||||||
- blue
|
- blue
|
||||||
selectedLineBgColor:
|
selectedLineBgColor:
|
||||||
@@ -28,6 +29,9 @@ Default path for the config file:
|
|||||||
mouseEvents: true
|
mouseEvents: true
|
||||||
skipUnstageLineWarning: false
|
skipUnstageLineWarning: false
|
||||||
git:
|
git:
|
||||||
|
paging:
|
||||||
|
colorArg: always
|
||||||
|
useConfig: false
|
||||||
merging:
|
merging:
|
||||||
# only applicable to unix users
|
# only applicable to unix users
|
||||||
manualCommit: false
|
manualCommit: false
|
||||||
@@ -53,6 +57,8 @@ Default path for the config file:
|
|||||||
nextBlock: '<right>' # goto the next block / panel
|
nextBlock: '<right>' # goto the next block / panel
|
||||||
prevBlock-alt: 'h' # goto the previous block / panel
|
prevBlock-alt: 'h' # goto the previous block / panel
|
||||||
nextBlock-alt: 'l' # goto the next block / panel
|
nextBlock-alt: 'l' # goto the next block / panel
|
||||||
|
nextMatch: 'n'
|
||||||
|
prevMatch: 'N'
|
||||||
optionMenu: 'x' # show help menu
|
optionMenu: 'x' # show help menu
|
||||||
optionMenu-alt1: '?' # show help menu
|
optionMenu-alt1: '?' # show help menu
|
||||||
select: '<space>'
|
select: '<space>'
|
||||||
@@ -75,6 +81,10 @@ Default path for the config file:
|
|||||||
createPatchOptionsMenu: '<c-p>'
|
createPatchOptionsMenu: '<c-p>'
|
||||||
nextTab: ']'
|
nextTab: ']'
|
||||||
prevTab: '['
|
prevTab: '['
|
||||||
|
nextScreenMode: '+'
|
||||||
|
prevScreenMode: '_'
|
||||||
|
undo: 'z'
|
||||||
|
redo: '<c-z>'
|
||||||
status:
|
status:
|
||||||
checkForUpdate: 'u'
|
checkForUpdate: 'u'
|
||||||
recentRepos: '<enter>'
|
recentRepos: '<enter>'
|
||||||
@@ -120,6 +130,7 @@ Default path for the config file:
|
|||||||
tagCommit: 'T'
|
tagCommit: 'T'
|
||||||
toggleDiffCommit: 'i'
|
toggleDiffCommit: 'i'
|
||||||
checkoutCommit: '<space>'
|
checkoutCommit: '<space>'
|
||||||
|
resetCherryPick: '<c-R>'
|
||||||
stash:
|
stash:
|
||||||
popStash: 'g'
|
popStash: 'g'
|
||||||
commitFiles:
|
commitFiles:
|
||||||
@@ -129,33 +140,32 @@ Default path for the config file:
|
|||||||
toggleDragSelect-alt: 'V'
|
toggleDragSelect-alt: 'V'
|
||||||
toggleSelectHunk: 'a'
|
toggleSelectHunk: 'a'
|
||||||
pickBothHunks: 'b'
|
pickBothHunks: 'b'
|
||||||
undo: 'z'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Platform Defaults:
|
## Platform Defaults
|
||||||
|
|
||||||
### Windows:
|
### Windows
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
os:
|
os:
|
||||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Linux:
|
### Linux
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
os:
|
os:
|
||||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||||
```
|
```
|
||||||
|
|
||||||
### OSX:
|
### OSX
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
os:
|
os:
|
||||||
openCommand: 'open {{filename}}'
|
openCommand: 'open {{filename}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Recommended Config Values:
|
### Recommended Config Values
|
||||||
|
|
||||||
for users of VSCode
|
for users of VSCode
|
||||||
|
|
||||||
@@ -164,7 +174,7 @@ for users of VSCode
|
|||||||
openCommand: 'code -r {{filename}}'
|
openCommand: 'code -r {{filename}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Color Attributes:
|
## Color Attributes
|
||||||
|
|
||||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||||
The available attributes are:
|
The available attributes are:
|
||||||
@@ -182,7 +192,7 @@ The available attributes are:
|
|||||||
- reverse # useful for high-contrast
|
- reverse # useful for high-contrast
|
||||||
- underline
|
- 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
|
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||||
|
|
||||||
@@ -199,15 +209,16 @@ If you have issues with a light terminal theme where you can't read / see the te
|
|||||||
- blue
|
- blue
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Coloring:
|
## Example Coloring
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Keybindings:
|
## Keybindings
|
||||||
|
|
||||||
For all possible keybinding options, check [Custom_Keybinding.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybinding.md)
|
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
|
||||||
|
|
||||||
#### Example Keybindings For Colemak Users:
|
|
||||||
```yaml
|
```yaml
|
||||||
keybinding:
|
keybinding:
|
||||||
universal:
|
universal:
|
||||||
@@ -215,6 +226,8 @@ For all possible keybinding options, check [Custom_Keybinding.md](https://github
|
|||||||
nextItem-alt: 'e'
|
nextItem-alt: 'e'
|
||||||
prevBlock-alt: 'n'
|
prevBlock-alt: 'n'
|
||||||
nextBlock-alt: 'i'
|
nextBlock-alt: 'i'
|
||||||
|
nextMatch: '='
|
||||||
|
prevMatch: '-'
|
||||||
new: 'k'
|
new: 'k'
|
||||||
edit: 'o'
|
edit: 'o'
|
||||||
openFile: 'O'
|
openFile: 'O'
|
||||||
@@ -232,3 +245,19 @@ For all possible keybinding options, check [Custom_Keybinding.md](https://github
|
|||||||
viewGitFlowOptions: 'I'
|
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`
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# 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).
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Undo/Redo in lazygit
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Keybindings:
|
||||||
|
'z' to undo, 'ctrl+z' to redo
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
If you're as clumsy as me you'll probably have felt the pain of botching an interactive rebase or doing a hard reset onto the wrong commit. Luckily, the reflog allows you to trace your steps and make things right again, but I personally can't stand trying to make sense of the reflog.
|
||||||
|
|
||||||
|
Lazygit can read through your reflog for you and walk back action by action so that you don't even need to read the reflog. If lazygit finds a reflog entry where you checked out a branch, we'll checkout the original branch. If the entry is from a commit being applied, we'll go back to the commit before that. If we hit an interactive rebase, we'll go back to the commit you were on just before you started it.
|
||||||
|
|
||||||
|
## You can even undo things you did outside of lazygit!
|
||||||
|
|
||||||
|
Because lazygit just uses the reflog to keep track of things, it doesn't matter whether you're trying to undo something you did in lazygit or directly on the command line. You can open lazygit for the first time and start undoing thing in your repo! Likewise, lazygit marks its undos/redos in the reflog so if you quit the application and come back, lazygit still knows where you're up to.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
There are limitations: firstly, lazygit can only undo things that are recorded in the reflog. That means changes to your working tree or stash aren't covered. Secondly, anything permanent you do like pushing to a remote can't be undone. Thirdly, actions like creating a branch won't be undone, because they're not stored in the reflog.
|
||||||
|
|
||||||
|
If you are mid-rebase, undo/redo is not supported, because the reflog doesn't enough contain information about what specific things have happened inside that rebase. If you want to undo out of a rebase, it's best to abort the rebase (the default keybinding for bringing up rebase options is 'm').
|
||||||
|
|
||||||
|
Undo/Redo is a new feature so if you find a bug let us know. The worst case scenario is that you'll just need to look at your reflog and manually put yourself back on track.
|
||||||
@@ -1,47 +1,31 @@
|
|||||||
# Lazygit menu
|
# Lazygit Keybindings
|
||||||
|
|
||||||
## Global
|
## Global Keybindings
|
||||||
|
|
||||||
<pre>
|
<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>m</kbd>: view merge/rebase options
|
||||||
<kbd>ctrl+p</kbd>: view custom patch options
|
<kbd>ctrl+p</kbd>: view custom patch options
|
||||||
<kbd>P</kbd>: push
|
<kbd>P</kbd>: push
|
||||||
<kbd>p</kbd>: pull
|
<kbd>p</kbd>: pull
|
||||||
<kbd>R</kbd>: refresh
|
<kbd>R</kbd>: refresh
|
||||||
|
<kbd>x</kbd>: open menu
|
||||||
|
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||||
|
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||||
|
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||||
|
<kbd>_</kbd>: prev screen mode
|
||||||
|
<kbd>:</kbd>: execute custom command
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Status
|
## Branches Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>e</kbd>: edit config file
|
<kbd>]</kbd>: next tab
|
||||||
<kbd>o</kbd>: open config file
|
<kbd>[</kbd>: previous tab
|
||||||
<kbd>u</kbd>: check for update
|
|
||||||
<kbd>s</kbd>: switch to a recent repo
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Files
|
## Branches Panel (Branches Tab)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>space</kbd>: checkout
|
<kbd>space</kbd>: checkout
|
||||||
@@ -50,12 +34,70 @@
|
|||||||
<kbd>F</kbd>: force checkout
|
<kbd>F</kbd>: force checkout
|
||||||
<kbd>n</kbd>: new branch
|
<kbd>n</kbd>: new branch
|
||||||
<kbd>d</kbd>: delete branch
|
<kbd>d</kbd>: delete branch
|
||||||
<kbd>r</kbd>: rebase branch
|
<kbd>r</kbd>: rebase checked-out branch onto this branch
|
||||||
<kbd>M</kbd>: merge into currently checked out 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>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>
|
</pre>
|
||||||
|
|
||||||
## Commits
|
## 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>: next tab
|
||||||
|
<kbd>[</kbd>: previous tab
|
||||||
|
<kbd>/</kbd>: start search
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Commits Panel (Commits Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>s</kbd>: squash down
|
<kbd>s</kbd>: squash down
|
||||||
@@ -76,46 +118,43 @@
|
|||||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||||
<kbd>enter</kbd>: view commit's files
|
<kbd>enter</kbd>: view commit's files
|
||||||
<kbd>space</kbd>: select commit to diff with another commit
|
<kbd>space</kbd>: checkout commit
|
||||||
|
<kbd>i</kbd>: select commit to diff with another commit
|
||||||
|
<kbd>T</kbd>: tag commit
|
||||||
|
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Stash
|
## Commits Panel (Reflog Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>space</kbd>: apply
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>g</kbd>: pop
|
<kbd>g</kbd>: view reset options
|
||||||
<kbd>d</kbd>: drop
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Commit files
|
## Files Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: go back
|
<kbd>c</kbd>: commit changes
|
||||||
<kbd>c</kbd>: checkout file
|
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||||
<kbd>d</kbd>: discard this commit's changes to this file
|
<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>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>
|
</pre>
|
||||||
|
|
||||||
## Main (Normal)
|
## Main Panel (Merging)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>esc</kbd>: return to files panel
|
<kbd>esc</kbd>: return to files panel
|
||||||
@@ -127,3 +166,71 @@
|
|||||||
<kbd>▼</kbd>: select bottom hunk
|
<kbd>▼</kbd>: select bottom hunk
|
||||||
<kbd>z</kbd>: undo
|
<kbd>z</kbd>: undo
|
||||||
</pre>
|
</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,46 +1,31 @@
|
|||||||
# Lazygit menu
|
# Lazygit Keybindings
|
||||||
|
|
||||||
## Global
|
## Global
|
||||||
|
|
||||||
<pre>
|
<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>m</kbd>: bekijk merge/rebase opties
|
||||||
|
<kbd>ctrl+p</kbd>: view custom patch options
|
||||||
<kbd>P</kbd>: push
|
<kbd>P</kbd>: push
|
||||||
<kbd>p</kbd>: pull
|
<kbd>p</kbd>: pull
|
||||||
<kbd>R</kbd>: verversen
|
<kbd>R</kbd>: verversen
|
||||||
|
<kbd>x</kbd>: open menu
|
||||||
|
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||||
|
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||||
|
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||||
|
<kbd>_</kbd>: prev screen mode
|
||||||
|
<kbd>:</kbd>: voor aangepast commando uit
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Status
|
## Branches Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>e</kbd>: verander config file
|
<kbd>]</kbd>: next tab
|
||||||
<kbd>o</kbd>: open config file
|
<kbd>[</kbd>: previous tab
|
||||||
<kbd>u</kbd>: check voor updates
|
|
||||||
<kbd>s</kbd>: wissel naar een recente repo
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Bestanden
|
## Branches Panel (Branches Tab)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>space</kbd>: uitchecken
|
<kbd>space</kbd>: uitchecken
|
||||||
@@ -51,10 +36,68 @@
|
|||||||
<kbd>d</kbd>: verwijder branch
|
<kbd>d</kbd>: verwijder branch
|
||||||
<kbd>r</kbd>: rebase branch
|
<kbd>r</kbd>: rebase branch
|
||||||
<kbd>M</kbd>: merge in met huidige checked out 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>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>
|
</pre>
|
||||||
|
|
||||||
## Commits
|
## 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>: next tab
|
||||||
|
<kbd>[</kbd>: previous tab
|
||||||
|
<kbd>/</kbd>: start search
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Commits Panel (Commits Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>s</kbd>: squash beneden
|
<kbd>s</kbd>: squash beneden
|
||||||
@@ -75,39 +118,43 @@
|
|||||||
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
||||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||||
<kbd>space</kbd>: select commit to diff with another commit
|
<kbd>space</kbd>: checkout commit
|
||||||
|
<kbd>i</kbd>: select commit to diff with another commit
|
||||||
|
<kbd>T</kbd>: tag commit
|
||||||
|
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Stash
|
## Commits Panel (Reflog Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>space</kbd>: toepassen
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>g</kbd>: pop
|
<kbd>g</kbd>: bekijk reset opties
|
||||||
<kbd>d</kbd>: drop
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Commit bestanden
|
## Bestanden Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: ga terug
|
<kbd>c</kbd>: Commit veranderingen
|
||||||
<kbd>c</kbd>: bestand uitchecken
|
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
<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>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>
|
</pre>
|
||||||
|
|
||||||
## Hoofd (Stage Lines/Hunks)
|
## Hoofd Panel (Merging)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||||
@@ -120,9 +167,70 @@
|
|||||||
<kbd>z</kbd>: ongedaan maken
|
<kbd>z</kbd>: ongedaan maken
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Hoofd (Normaal)
|
## Hoofd Panel (Normaal)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
|
<kbd> ̄</kbd>: scroll omlaag (fn+up)
|
||||||
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
|
<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
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -1,46 +1,31 @@
|
|||||||
# Lazygit menu
|
# Lazygit Keybindings
|
||||||
|
|
||||||
## Globalne
|
## Globalne
|
||||||
|
|
||||||
<pre>
|
<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>m</kbd>: view merge/rebase options
|
||||||
|
<kbd>ctrl+p</kbd>: view custom patch options
|
||||||
<kbd>P</kbd>: push
|
<kbd>P</kbd>: push
|
||||||
<kbd>p</kbd>: pull
|
<kbd>p</kbd>: pull
|
||||||
<kbd>R</kbd>: odśwież
|
<kbd>R</kbd>: odśwież
|
||||||
|
<kbd>x</kbd>: open menu
|
||||||
|
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||||
|
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||||
|
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||||
|
<kbd>_</kbd>: prev screen mode
|
||||||
|
<kbd>:</kbd>: execute custom command
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Status
|
## Gałęzie Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
<kbd>]</kbd>: next tab
|
||||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
<kbd>[</kbd>: previous tab
|
||||||
<kbd>u</kbd>: sprawdź aktualizacje
|
|
||||||
<kbd>s</kbd>: switch to a recent repo
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Pliki
|
## Gałęzie Panel (Branches Tab)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>space</kbd>: przełącz
|
<kbd>space</kbd>: przełącz
|
||||||
@@ -51,10 +36,68 @@
|
|||||||
<kbd>d</kbd>: usuń gałąź
|
<kbd>d</kbd>: usuń gałąź
|
||||||
<kbd>r</kbd>: rebase branch
|
<kbd>r</kbd>: rebase branch
|
||||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
<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>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>
|
</pre>
|
||||||
|
|
||||||
## Commity
|
## 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>: next tab
|
||||||
|
<kbd>[</kbd>: previous tab
|
||||||
|
<kbd>/</kbd>: start search
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Commity Panel (Commits Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>s</kbd>: ściśnij w dół
|
<kbd>s</kbd>: ściśnij w dół
|
||||||
@@ -75,46 +118,43 @@
|
|||||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||||
<kbd>enter</kbd>: view commit's files
|
<kbd>enter</kbd>: view commit's files
|
||||||
<kbd>space</kbd>: select commit to diff with another commit
|
<kbd>space</kbd>: checkout commit
|
||||||
|
<kbd>i</kbd>: select commit to diff with another commit
|
||||||
|
<kbd>T</kbd>: tag commit
|
||||||
|
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Schowek
|
## Commity Panel (Reflog Tab)
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>space</kbd>: zastosuj
|
<kbd>space</kbd>: checkout commit
|
||||||
<kbd>g</kbd>: wyciągnij
|
<kbd>g</kbd>: view reset options
|
||||||
<kbd>d</kbd>: porzuć
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
## Commit files
|
## Pliki Panel
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<kbd>esc</kbd>: go back
|
<kbd>c</kbd>: commituj zmiany
|
||||||
<kbd>c</kbd>: checkout file
|
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||||
<kbd>d</kbd>: discard this commit's changes to this file
|
<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>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>
|
</pre>
|
||||||
|
|
||||||
## Main (Normal)
|
## Main Panel (Merging)
|
||||||
|
|
||||||
<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>
|
<pre>
|
||||||
<kbd>esc</kbd>: wróć do panelu plików
|
<kbd>esc</kbd>: wróć do panelu plików
|
||||||
@@ -124,5 +164,73 @@
|
|||||||
<kbd>►</kbd>: select next conflict
|
<kbd>►</kbd>: select next conflict
|
||||||
<kbd>▲</kbd>: select top hunk
|
<kbd>▲</kbd>: select top hunk
|
||||||
<kbd>▼</kbd>: select bottom hunk
|
<kbd>▼</kbd>: select bottom hunk
|
||||||
<kbd>z</kbd>: undo
|
<kbd>z</kbd>: cofnij
|
||||||
|
</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>
|
</pre>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 650 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 539 KiB |
@@ -1,9 +1,10 @@
|
|||||||
module github.com/jesseduffield/lazygit
|
module github.com/jesseduffield/lazygit
|
||||||
|
|
||||||
go 1.13
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||||
|
github.com/creack/pty v1.1.10-0.20191209115840-8ab47f72e854
|
||||||
github.com/fatih/color v1.7.0
|
github.com/fatih/color v1.7.0
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/go-errors/errors v1.0.1
|
github.com/go-errors/errors v1.0.1
|
||||||
@@ -11,9 +12,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.3.1 // indirect
|
github.com/google/go-cmp v0.3.1 // indirect
|
||||||
github.com/integrii/flaggy v1.4.0
|
github.com/integrii/flaggy v1.4.0
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed
|
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a
|
||||||
github.com/jesseduffield/pty v1.2.1
|
|
||||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
|||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/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/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/creack/pty v1.1.10-0.20191209115840-8ab47f72e854 h1:NB4neYMzyBsw52kUdkTrQm4Q05ErObCdwLvJptpfJSc=
|
||||||
|
github.com/creack/pty v1.1.10-0.20191209115840-8ab47f72e854/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -54,7 +56,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
|
||||||
github.com/golang/protobuf v1.3.1/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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@@ -77,28 +78,8 @@ 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.4.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532 h1:V1Lk2rm5/p27NjnlF2ezzkxDaisHNcveMNueSD7RYgs=
|
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a h1:JSORQue6V4bMppr22dtUuYX+w79cgupo66PcGZ9ijlU=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351 h1:+sSqd2YotacWt+1MNRN8ZmXnYoiJeblZeptzKiHIyv0=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039 h1:CVhilJ8ZdN7GmAI+fbH9829Cp/8hbK7Lijbd4VaNgo0=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac h1:vp7I0RpFq4L46nFA9QQokzhFgr68LRGtwDO9xfq4F+A=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5 h1:tE0w3tuL/bj1o5VMhjjE0ep6i7Fva+RYjKcMFcniJEY=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe h1:UQyebauOcBzbGq32kTXwEyuJaqp3BkI8JoCrGs2jijU=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200223105115-3e1f0f7c3efe/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed h1:glGs+mzPZOl1iHiUsBW3918WeFwqsbQQ/jtLkkQXDi4=
|
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
|
||||||
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
|
||||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
|
||||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
|
||||||
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-20190630083001-9dd53af7214e h1:tth7wr6+sfSbdpRWWrwvLYyS56HyIRVfq0Qcl2h28wM=
|
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 h1:iBBk1lhFwjwJw//J2m1yyz9S368GeXQTpMVACTyQMh0=
|
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 h1:iBBk1lhFwjwJw//J2m1yyz9S368GeXQTpMVACTyQMh0=
|
||||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
@@ -112,7 +93,6 @@ github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v
|
|||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
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/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.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 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/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -130,8 +110,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
|||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
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 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
@@ -151,7 +129,6 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
|
|||||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
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/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
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.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 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||||
@@ -175,7 +152,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
|||||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
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/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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
@@ -184,17 +160,14 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
|
|||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
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/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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
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/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 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
@@ -229,7 +202,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
|||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-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 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@@ -244,7 +216,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-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 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@@ -264,7 +235,6 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-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-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 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||||
@@ -286,7 +256,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
|
|||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
@@ -20,11 +19,6 @@ var (
|
|||||||
buildSource = "unknown"
|
buildSource = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
func projectPath(path string) string {
|
|
||||||
gopath := os.Getenv("GOPATH")
|
|
||||||
return filepath.FromSlash(gopath + "/src/github.com/jesseduffield/lazygit/" + path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||||
|
|
||||||
|
|||||||
+8
-41
@@ -1,47 +1,14 @@
|
|||||||
package commands
|
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
|
// Branch : A git branch
|
||||||
// duplicating this for now
|
// duplicating this for now
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
Name string
|
Name string
|
||||||
Recency string
|
// the displayname is something like '(HEAD detached at 123asdf)', whereas in that case the name would be '123asdf'
|
||||||
Pushables string
|
DisplayName string
|
||||||
Pullables string
|
Recency string
|
||||||
Selected bool
|
Pushables string
|
||||||
}
|
Pullables string
|
||||||
|
UpstreamName string
|
||||||
// GetDisplayStrings returns the display string of branch
|
Head bool
|
||||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
|
||||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
|
||||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
|
||||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{b.Recency, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// context:
|
// context:
|
||||||
@@ -36,113 +34,108 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchLis
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
func (b *BranchListBuilder) obtainBranches() []*Branch {
|
||||||
branchName, err := b.GitCommand.CurrentBranchName()
|
cmdStr := `git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`
|
||||||
if err != nil {
|
output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr)
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Branch{Name: strings.TrimSpace(branchName)}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
branchLines := utils.SplitLines(rawString)
|
|
||||||
for _, line := range branchLines {
|
|
||||||
recency, branchName := branchInfoFromLine(line)
|
|
||||||
if branchName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
branch := &Branch{Name: branchName, Recency: recency}
|
|
||||||
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
bIter.ForEach(func(b *plumbing.Reference) error {
|
|
||||||
name := b.Name().Short()
|
trimmedOutput := strings.TrimSpace(output)
|
||||||
branches = append(branches, &Branch{Name: name})
|
outputLines := strings.Split(trimmedOutput, "\n")
|
||||||
return nil
|
branches := make([]*Branch, 0, len(outputLines))
|
||||||
})
|
for _, line := range outputLines {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(line, SEPARATION_CHAR)
|
||||||
|
|
||||||
|
name := split[1]
|
||||||
|
branch := &Branch{
|
||||||
|
Name: name,
|
||||||
|
Pullables: "?",
|
||||||
|
Pushables: "?",
|
||||||
|
Head: split[0] == "*",
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamName := split[2]
|
||||||
|
if upstreamName == "" {
|
||||||
|
branches = append(branches, branch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
branch.UpstreamName = upstreamName
|
||||||
|
|
||||||
|
track := split[3]
|
||||||
|
re := regexp.MustCompile(`ahead (\d+)`)
|
||||||
|
match := re.FindStringSubmatch(track)
|
||||||
|
if len(match) > 1 {
|
||||||
|
branch.Pushables = match[1]
|
||||||
|
} else {
|
||||||
|
branch.Pushables = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
re = regexp.MustCompile(`behind (\d+)`)
|
||||||
|
match = re.FindStringSubmatch(track)
|
||||||
|
if len(match) > 1 {
|
||||||
|
branch.Pullables = match[1]
|
||||||
|
} else {
|
||||||
|
branch.Pullables = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
branches = append(branches, branch)
|
||||||
|
}
|
||||||
|
|
||||||
return branches
|
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
|
// Build the list of branches for the current repo
|
||||||
func (b *BranchListBuilder) Build() []*Branch {
|
func (b *BranchListBuilder) Build() []*Branch {
|
||||||
branches := make([]*Branch, 0)
|
branches := b.obtainBranches()
|
||||||
head := b.obtainCurrentBranch()
|
|
||||||
safeBranches := b.obtainSafeBranches()
|
|
||||||
|
|
||||||
reflogBranches := b.obtainReflogBranches()
|
reflogBranches := b.obtainReflogBranches()
|
||||||
for i, reflogBranch := range reflogBranches {
|
|
||||||
reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches)
|
// 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 branch.Head {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true)
|
branches = append(branchesWithRecency, branches...)
|
||||||
branches = b.appendNewBranches(branches, safeBranches, branches, false)
|
|
||||||
|
|
||||||
if len(branches) == 0 || branches[0].Name != head.Name {
|
foundHead := false
|
||||||
branches = append([]*Branch{head}, branches...)
|
for i, branch := range branches {
|
||||||
|
if branch.Head {
|
||||||
|
foundHead = true
|
||||||
|
branch.Recency = " *"
|
||||||
|
branches = append(branches[0:i], branches[i+1:]...)
|
||||||
|
branches = append([]*Branch{branch}, branches...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundHead {
|
||||||
|
currentBranchName, currentBranchDisplayName, err := b.GitCommand.CurrentBranchName()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
branches = append([]*Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...)
|
||||||
}
|
}
|
||||||
|
|
||||||
branches[0].Recency = " *"
|
|
||||||
|
|
||||||
return branches
|
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
|
// 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
|
// useful information from that into timeNumber, timeUnit, and branchName
|
||||||
func branchInfoFromLine(line string) (string, string) {
|
func branchInfoFromLine(line string) (string, string) {
|
||||||
@@ -172,3 +165,30 @@ func abbreviatedTimeUnit(timeUnit string) string {
|
|||||||
}
|
}
|
||||||
return timeUnitMap[timeUnit]
|
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
|
||||||
|
}
|
||||||
|
|||||||
+12
-61
@@ -1,69 +1,20 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Commit : A git commit
|
// Commit : A git commit
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
Sha string
|
Sha string
|
||||||
Name string
|
Name string
|
||||||
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||||
DisplayString string
|
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
Tags []string
|
||||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||||
Tags []string
|
Author string
|
||||||
|
Date string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings is a function.
|
func (c *Commit) ShortSha() string {
|
||||||
func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
if len(c.Sha) < 8 {
|
||||||
red := color.New(color.FgRed)
|
return c.Sha
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
return c.Sha[:8]
|
||||||
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,10 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommitFile : A git commit file
|
// CommitFile : A git commit file
|
||||||
type CommitFile struct {
|
type CommitFile struct {
|
||||||
Sha string
|
Sha string
|
||||||
@@ -22,21 +17,3 @@ const (
|
|||||||
// PART is for when you're only talking about specific lines that have been modified
|
// PART is for when you're only talking about specific lines that have been modified
|
||||||
PART
|
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)}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,6 +24,8 @@ import (
|
|||||||
// if we find out we need to use one of these functions in the git.go file, we
|
// 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
|
// 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
|
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||||
type CommitListBuilder struct {
|
type CommitListBuilder struct {
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
@@ -45,42 +48,36 @@ func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameAndTag takes a line from a git log and extracts the sha, message and tag (if present)
|
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
|
||||||
// example inputs:
|
// then puts them into a commit object
|
||||||
// 66e6369c284e96ed5af5 (tag: v0.14.4) allow fastforwarding the current branch
|
// example input:
|
||||||
// 32e650e0bb3f4327749f (HEAD -> show-tags) this is my commit
|
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||||
// 32e650e0bb3f4327749e this is my other commit
|
func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||||
func (c *CommitListBuilder) commitLineParts(line string) (string, string, []string) {
|
split := strings.Split(line, SEPARATION_CHAR)
|
||||||
re := regexp.MustCompile(`(\w+) (.*)`)
|
|
||||||
shaMatch := re.FindStringSubmatch(line)
|
|
||||||
|
|
||||||
if len(shaMatch) <= 1 {
|
sha := split[0]
|
||||||
return line, "", nil
|
date := split[1]
|
||||||
}
|
author := split[2]
|
||||||
sha := shaMatch[1]
|
extraInfo := strings.TrimSpace(split[3])
|
||||||
rest := shaMatch[2]
|
message := strings.Join(split[4:], SEPARATION_CHAR)
|
||||||
|
tags := []string{}
|
||||||
|
|
||||||
if !strings.HasPrefix(rest, "(") {
|
if extraInfo != "" {
|
||||||
return sha, rest, nil
|
re := regexp.MustCompile(`tag: ([^,\)]+)`)
|
||||||
|
tagMatch := re.FindStringSubmatch(extraInfo)
|
||||||
|
if len(tagMatch) > 1 {
|
||||||
|
tags = append(tags, tagMatch[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
re = regexp.MustCompile(`\((.*)\) (.*)`)
|
return &Commit{
|
||||||
|
Sha: sha,
|
||||||
parensMatch := re.FindStringSubmatch(rest)
|
Name: message,
|
||||||
if len(parensMatch) <= 1 {
|
Tags: tags,
|
||||||
return sha, rest, nil
|
ExtraInfo: extraInfo,
|
||||||
|
Date: date,
|
||||||
|
Author: author,
|
||||||
}
|
}
|
||||||
|
|
||||||
notes := parensMatch[1]
|
|
||||||
message := parensMatch[2]
|
|
||||||
re = regexp.MustCompile(`tag: ([^,]+)`)
|
|
||||||
tagMatch := re.FindStringSubmatch(notes)
|
|
||||||
if len(tagMatch) <= 1 {
|
|
||||||
return sha, message, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tag := tagMatch[1]
|
|
||||||
return sha, message, []string{tag}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommits obtains the commits of the current branch
|
// GetCommits obtains the commits of the current branch
|
||||||
@@ -103,21 +100,20 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unpushedCommits := c.getUnpushedCommits()
|
unpushedCommits := c.getUnpushedCommits()
|
||||||
log := c.getLog(limit)
|
cmd := c.getLogCmd(limit)
|
||||||
|
|
||||||
// now we can split it up and turn it into commits
|
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||||
for _, line := range utils.SplitLines(log) {
|
commit := c.extractCommitFromLine(line)
|
||||||
sha, name, tags := c.commitLineParts(line)
|
_, unpushed := unpushedCommits[commit.ShortSha()]
|
||||||
_, unpushed := unpushedCommits[sha]
|
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||||
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
commits = append(commits, commit)
|
||||||
commits = append(commits, &Commit{
|
|
||||||
Sha: sha,
|
return false, nil
|
||||||
Name: name,
|
})
|
||||||
Status: status,
|
if err != nil {
|
||||||
DisplayString: line,
|
return nil, err
|
||||||
Tags: tags,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rebaseMode != "" {
|
if rebaseMode != "" {
|
||||||
currentCommit := commits[len(rebasingCommits)]
|
currentCommit := commits[len(rebasingCommits)]
|
||||||
blue := color.New(color.FgYellow)
|
blue := color.New(color.FgYellow)
|
||||||
@@ -130,11 +126,6 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
commits, err = c.setCommitCherryPickStatuses(commits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, commit := range commits {
|
for _, commit := range commits {
|
||||||
for _, entry := range c.DiffEntries {
|
for _, entry := range c.DiffEntries {
|
||||||
if entry.Sha == commit.Sha {
|
if entry.Sha == commit.Sha {
|
||||||
@@ -276,19 +267,8 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commi
|
|||||||
return commits, nil
|
return commits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommitListBuilder) setCommitCherryPickStatuses(commits []*Commit) ([]*Commit, error) {
|
|
||||||
for _, commit := range commits {
|
|
||||||
for _, cherryPickedCommit := range c.CherryPickedCommits {
|
|
||||||
if commit.Sha == cherryPickedCommit.Sha {
|
|
||||||
commit.Copied = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commits, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommitListBuilder) getMergeBase() (string, error) {
|
func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||||
currentBranch, err := c.GitCommand.CurrentBranchName()
|
currentBranch, _, err := c.GitCommand.CurrentBranchName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -307,7 +287,7 @@ func (c *CommitListBuilder) getMergeBase() (string, error) {
|
|||||||
// to the remote branch of the current branch, a map is returned to ease look up
|
// to the remote branch of the current branch, a map is returned to ease look up
|
||||||
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||||
pushables := map[string]bool{}
|
pushables := map[string]bool{}
|
||||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit")
|
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit --abbrev=8")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pushables
|
return pushables
|
||||||
}
|
}
|
||||||
@@ -319,17 +299,11 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getLog gets the git log.
|
// getLog gets the git log.
|
||||||
func (c *CommitListBuilder) getLog(limit bool) string {
|
func (c *CommitListBuilder) getLogCmd(limit bool) *exec.Cmd {
|
||||||
limitFlag := ""
|
limitFlag := ""
|
||||||
if limit {
|
if limit {
|
||||||
limitFlag = "-30"
|
limitFlag = "-300"
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --decorate --oneline %s --abbrev=%d", limitFlag, 20))
|
return c.OSCommand.ExecutableFromString(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))
|
||||||
if err != nil {
|
|
||||||
// assume if there is an error there are no commits yet for this branch
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,170 +149,3 @@ 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", "--decorate", "--oneline", "-30", "--abbrev=20"}, 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", "--decorate", "--oneline", "-30", "--abbrev=20"}, 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(true))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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", "--decorate", "--oneline", "-30", "--abbrev=20"}, 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", "--decorate", "--oneline", "-30", "--abbrev=20"}, 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", "--decorate", "--oneline", "-30", "--abbrev=20"}, 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 "branch":
|
|
||||||
assert.EqualValues(t, []string{"branch", "--contains"}, args)
|
|
||||||
// here too
|
|
||||||
return exec.Command("test")
|
|
||||||
case "rev-parse":
|
|
||||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
|
||||||
// here too
|
|
||||||
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(true))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
|
||||||
"github.com/jesseduffield/pty"
|
"github.com/creack/pty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
|
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import "github.com/fatih/color"
|
|
||||||
|
|
||||||
// File : A file from git status
|
// File : A file from git status
|
||||||
// duplicating this for now
|
// duplicating this for now
|
||||||
type File struct {
|
type File struct {
|
||||||
@@ -16,23 +14,3 @@ type File struct {
|
|||||||
Type string // one of 'file', 'directory', and 'other'
|
Type string // one of 'file', 'directory', and 'other'
|
||||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
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}
|
|
||||||
}
|
|
||||||
|
|||||||
+117
-47
@@ -7,6 +7,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
|||||||
|
|
||||||
// GetStashEntryDiff stash diff
|
// GetStashEntryDiff stash diff
|
||||||
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||||
return fmt.Sprintf("git stash show -p --color stash@{%d}", index)
|
return fmt.Sprintf("git stash show -p --color=%s stash@{%d}", c.colorArg(), index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusFiles git status files
|
// GetStatusFiles git status files
|
||||||
@@ -321,28 +322,38 @@ func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCrede
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResetToCommit reset to commit
|
// ResetToCommit reset to commit
|
||||||
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
|
func (c *GitCommand) ResetToCommit(sha string, strength string, options RunCommandOptions) error {
|
||||||
return c.OSCommand.RunCommand("git reset --%s %s", strength, sha)
|
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBranch create new branch
|
// NewBranch create new branch
|
||||||
func (c *GitCommand) NewBranch(name string) error {
|
func (c *GitCommand) NewBranch(name string, baseBranch string) error {
|
||||||
return c.OSCommand.RunCommand("git checkout -b %s", name)
|
return c.OSCommand.RunCommand("git checkout -b %s %s", name, baseBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentBranchName is a function.
|
// CurrentBranchName get the current branch name and displayname.
|
||||||
func (c *GitCommand) CurrentBranchName() (string, error) {
|
// the first returned string is the name and the second is the displayname
|
||||||
|
// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
|
||||||
|
func (c *GitCommand) CurrentBranchName() (string, string, error) {
|
||||||
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
branchName, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||||
if err != nil || branchName == "HEAD\n" {
|
if err == nil && branchName != "HEAD\n" {
|
||||||
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
trimmedBranchName := strings.TrimSpace(branchName)
|
||||||
if err != nil {
|
return trimmedBranchName, trimmedBranchName, nil
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
|
||||||
match := re.FindStringSubmatch(output)
|
|
||||||
branchName = match[1]
|
|
||||||
}
|
}
|
||||||
return utils.TrimTrailingNewline(branchName), nil
|
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
for _, line := range utils.SplitLines(output) {
|
||||||
|
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||||
|
match := re.FindStringSubmatch(line)
|
||||||
|
if len(match) > 0 {
|
||||||
|
branchName = match[1]
|
||||||
|
displayBranchName := match[0][2:]
|
||||||
|
return branchName, displayBranchName, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "HEAD", "HEAD", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBranch delete branch
|
// DeleteBranch delete branch
|
||||||
@@ -521,12 +532,17 @@ func (c *GitCommand) DiscardUnstagedFileChanges(file *File) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
||||||
func (c *GitCommand) Checkout(branch string, force bool) error {
|
type CheckoutOptions struct {
|
||||||
|
Force bool
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
|
||||||
forceArg := ""
|
forceArg := ""
|
||||||
if force {
|
if options.Force {
|
||||||
forceArg = "--force "
|
forceArg = "--force "
|
||||||
}
|
}
|
||||||
return c.OSCommand.RunCommand("git checkout %s %s", forceArg, branch)
|
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout %s %s", forceArg, branch), RunCommandOptions{EnvVars: options.EnvVars})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
// PrepareCommitSubProcess prepares a subprocess for `git commit`
|
||||||
@@ -558,11 +574,11 @@ func (c *GitCommand) Ignore(filename string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) ShowCmdStr(sha string) string {
|
func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||||
return fmt.Sprintf("git show --color --no-renames --stat -p %s", sha)
|
return fmt.Sprintf("git show --color=%s --no-renames --stat -p %s", c.colorArg(), sha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
||||||
return fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium %s", branchName)
|
return fmt.Sprintf("git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium %s --", branchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteURL returns current repo remote url
|
// GetRemoteURL returns current repo remote url
|
||||||
@@ -591,7 +607,7 @@ func (c *GitCommand) Diff(file *File, plain bool, cached bool) string {
|
|||||||
func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
||||||
cachedArg := ""
|
cachedArg := ""
|
||||||
trackedArg := "--"
|
trackedArg := "--"
|
||||||
colorArg := "--color"
|
colorArg := c.colorArg()
|
||||||
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
||||||
fileName := c.OSCommand.Quote(split[len(split)-1])
|
fileName := c.OSCommand.Quote(split[len(split)-1])
|
||||||
if cached {
|
if cached {
|
||||||
@@ -601,10 +617,10 @@ func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
|||||||
trackedArg = "--no-index /dev/null"
|
trackedArg = "--no-index /dev/null"
|
||||||
}
|
}
|
||||||
if plain {
|
if plain {
|
||||||
colorArg = ""
|
colorArg = "never"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("git diff --stat -p %s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
return fmt.Sprintf("git diff --color=%s %s %s %s", colorArg, cachedArg, trackedArg, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
|
||||||
@@ -865,7 +881,7 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
|
|||||||
|
|
||||||
// GetCommitFiles get the specified commit files
|
// GetCommitFiles get the specified commit files
|
||||||
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
||||||
files, err := c.OSCommand.RunCommandWithOutput("git show --pretty= --name-only --no-renames %s", commitSha)
|
files, err := c.OSCommand.RunCommandWithOutput("git diff-tree --no-commit-id --name-only -r --no-renames %s", commitSha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -896,12 +912,12 @@ func (c *GitCommand) ShowCommitFile(commitSha, fileName string, plain bool) (str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) ShowCommitFileCmdStr(commitSha, fileName string, plain bool) string {
|
func (c *GitCommand) ShowCommitFileCmdStr(commitSha, fileName string, plain bool) string {
|
||||||
colorArg := "--color"
|
colorArg := c.colorArg()
|
||||||
if plain {
|
if plain {
|
||||||
colorArg = ""
|
colorArg = "never"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("git show --no-renames %s %s -- %s", colorArg, commitSha, fileName)
|
return fmt.Sprintf("git show --no-renames --color=%s %s -- %s", colorArg, commitSha, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckoutFile checks out the file for the given commit
|
// CheckoutFile checks out the file for the given commit
|
||||||
@@ -967,7 +983,7 @@ func (c *GitCommand) ResetSoft(ref string) error {
|
|||||||
|
|
||||||
// DiffCommits show diff between commits
|
// DiffCommits show diff between commits
|
||||||
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput("git diff --color %s %s", sha1, sha2)
|
return c.OSCommand.RunCommandWithOutput("git diff --color=%s --stat -p %s %s", c.colorArg(), sha1, sha2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||||
@@ -1104,27 +1120,81 @@ func (c *GitCommand) FetchRemote(remoteName string) error {
|
|||||||
return c.OSCommand.RunCommand("git fetch %s", remoteName)
|
return c.OSCommand.RunCommand("git fetch %s", remoteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
// GetNewReflogCommits only returns the new reflog commits since the given lastReflogCommit
|
||||||
output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20")
|
// if none is passed (i.e. it's value is nil) then we get all the reflog commits
|
||||||
|
func (c *GitCommand) GetNewReflogCommits(lastReflogCommit *Commit) ([]*Commit, error) {
|
||||||
|
output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20 --date=iso")
|
||||||
|
if err != nil {
|
||||||
|
// assume error means we have no reflog
|
||||||
|
return []*Commit{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
commits := make([]*Commit, 0, len(lines))
|
||||||
|
re := regexp.MustCompile(`(\w+).*HEAD@\{([^\}]+)\}: (.*)`)
|
||||||
|
cmd := c.OSCommand.ExecutableFromString("git reflog --abbrev=20 --date=iso")
|
||||||
|
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||||
|
match := re.FindStringSubmatch(line)
|
||||||
|
if len(match) <= 1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
commit := &Commit{
|
||||||
|
Sha: match[1],
|
||||||
|
Name: match[3],
|
||||||
|
Date: match[2],
|
||||||
|
Status: "reflog",
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.Date == lastReflogCommit.Date {
|
||||||
|
// after this point we already have these reflogs loaded so we'll simply return the new ones
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
commits = append(commits, commit)
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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)
|
||||||
|
}
|
||||||
|
|||||||
+44
-19
@@ -628,7 +628,7 @@ func TestGitCommandResetToCommit(t *testing.T) {
|
|||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard"))
|
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", RunCommandOptions{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGitCommandNewBranch is a function.
|
// TestGitCommandNewBranch is a function.
|
||||||
@@ -636,12 +636,12 @@ func TestGitCommandNewBranch(t *testing.T) {
|
|||||||
gitCmd := NewDummyGitCommand()
|
gitCmd := NewDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"checkout", "-b", "test"}, args)
|
assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, gitCmd.NewBranch("test"))
|
assert.NoError(t, gitCmd.NewBranch("test", "master"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGitCommandDeleteBranch is a function.
|
// TestGitCommandDeleteBranch is a function.
|
||||||
@@ -1439,7 +1439,7 @@ func TestGitCommandCheckout(t *testing.T) {
|
|||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
gitCmd := NewDummyGitCommand()
|
gitCmd := NewDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = s.command
|
gitCmd.OSCommand.command = s.command
|
||||||
s.test(gitCmd.Checkout("test", s.force))
|
s.test(gitCmd.Checkout("test", CheckoutOptions{Force: s.force}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1449,7 +1449,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
|
|||||||
gitCmd := NewDummyGitCommand()
|
gitCmd := NewDummyGitCommand()
|
||||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test"}, args)
|
assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
}
|
}
|
||||||
@@ -1473,7 +1473,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
"Default case",
|
"Default case",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"diff", "--stat", "-p", "--color", "--", "test.txt"}, args)
|
assert.EqualValues(t, []string{"diff", "--color=", "--", "test.txt"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1489,7 +1489,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
"cached",
|
"cached",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"diff", "--stat", "-p", "--color", "--cached", "--", "test.txt"}, args)
|
assert.EqualValues(t, []string{"diff", "--color=", "--cached", "--", "test.txt"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1505,7 +1505,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
"plain",
|
"plain",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"diff", "--stat", "-p", "--", "test.txt"}, args)
|
assert.EqualValues(t, []string{"diff", "--color=never", "--", "test.txt"}, args)
|
||||||
|
|
||||||
return exec.Command("echo")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1521,7 +1521,7 @@ func TestGitCommandDiff(t *testing.T) {
|
|||||||
"File not tracked and file has no staged changes",
|
"File not tracked and file has no staged changes",
|
||||||
func(cmd string, args ...string) *exec.Cmd {
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
assert.EqualValues(t, "git", cmd)
|
assert.EqualValues(t, "git", cmd)
|
||||||
assert.EqualValues(t, []string{"diff", "--stat", "-p", "--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")
|
return exec.Command("echo")
|
||||||
},
|
},
|
||||||
@@ -1549,7 +1549,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
command func(string, ...string) *exec.Cmd
|
command func(string, ...string) *exec.Cmd
|
||||||
test func(string, error)
|
test func(string, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
@@ -1559,9 +1559,10 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
assert.Equal(t, "git", cmd)
|
assert.Equal(t, "git", cmd)
|
||||||
return exec.Command("echo", "master")
|
return exec.Command("echo", "master")
|
||||||
},
|
},
|
||||||
func(output string, err error) {
|
func(name string, displayname string, err error) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, "master", output)
|
assert.EqualValues(t, "master", name)
|
||||||
|
assert.EqualValues(t, "master", displayname)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1580,9 +1581,32 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
func(output string, err error) {
|
func(name string, displayname string, err error) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, "master", output)
|
assert.EqualValues(t, "master", name)
|
||||||
|
assert.EqualValues(t, "master", displayname)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handles a detached head",
|
||||||
|
func(cmd string, args ...string) *exec.Cmd {
|
||||||
|
assert.EqualValues(t, "git", cmd)
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
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", "* (HEAD detached at 123abcd)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(name string, displayname string, err error) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, "123abcd", name)
|
||||||
|
assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1591,9 +1615,10 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
|||||||
assert.Equal(t, "git", cmd)
|
assert.Equal(t, "git", cmd)
|
||||||
return exec.Command("test")
|
return exec.Command("test")
|
||||||
},
|
},
|
||||||
func(output string, err error) {
|
func(name string, displayname string, err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualValues(t, "", output)
|
assert.EqualValues(t, "", name)
|
||||||
|
assert.EqualValues(t, "", displayname)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1871,7 +1896,7 @@ func TestGitCommandShowCommitFile(t *testing.T) {
|
|||||||
"hello.txt",
|
"hello.txt",
|
||||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||||
{
|
{
|
||||||
Expect: "git show --no-renames 123456 -- hello.txt",
|
Expect: "git show --no-renames --color=never 123456 -- hello.txt",
|
||||||
Replace: "echo -n hello",
|
Replace: "echo -n hello",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -1907,7 +1932,7 @@ func TestGitCommandGetCommitFiles(t *testing.T) {
|
|||||||
"123456",
|
"123456",
|
||||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||||
{
|
{
|
||||||
Expect: "git show --pretty= --name-only --no-renames 123456",
|
Expect: "git diff-tree --no-commit-id --name-only -r --no-renames 123456",
|
||||||
Replace: "echo 'hello\nworld'",
|
Replace: "echo 'hello\nworld'",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -2130,7 +2155,7 @@ func TestGitCommandSkipEditorCommand(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd.RunSkipEditorCommand("true")
|
_ = cmd.RunSkipEditorCommand("true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindDotGitDir(t *testing.T) {
|
func TestFindDotGitDir(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -64,6 +65,22 @@ func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) {
|
|||||||
c.beforeExecuteCmd = cmd
|
c.beforeExecuteCmd = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RunCommandOptions struct {
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OSCommand) RunCommandWithOutputWithOptions(command string, options RunCommandOptions) (string, error) {
|
||||||
|
c.Log.WithField("command", command).Info("RunCommand")
|
||||||
|
cmd := c.ExecutableFromString(command)
|
||||||
|
cmd.Env = append(cmd.Env, options.EnvVars...)
|
||||||
|
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OSCommand) RunCommandWithOptions(command string, options RunCommandOptions) error {
|
||||||
|
_, err := c.RunCommandWithOutputWithOptions(command, options)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// RunCommandWithOutput wrapper around commands returning their output and error
|
// RunCommandWithOutput wrapper around commands returning their output and error
|
||||||
// NOTE: If you don't pass any formatArgs we'll just use the command directly,
|
// 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
|
// however there's a bizarre compiler error/warning when you pass in a formatString
|
||||||
@@ -401,3 +418,39 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) error {
|
||||||
|
stdoutPipe, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdoutPipe)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
stop, err := onLine(line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stop {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service is a service that repository is on (Github, Bitbucket, ...)
|
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||||
@@ -26,27 +27,63 @@ type RepoInformation struct {
|
|||||||
Repository string
|
Repository string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServices() []*Service {
|
// NewService builds a Service based on the host type
|
||||||
return []*Service{
|
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
|
||||||
{
|
var service *Service
|
||||||
Name: "github.com",
|
|
||||||
PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
|
switch typeName {
|
||||||
},
|
case "github":
|
||||||
{
|
service = &Service{
|
||||||
Name: "bitbucket.org",
|
Name: repositoryDomain,
|
||||||
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
|
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/compare/%s?expand=1"),
|
||||||
},
|
}
|
||||||
{
|
case "bitbucket":
|
||||||
Name: "gitlab.com",
|
service = &Service{
|
||||||
PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
|
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"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// NewPullRequest creates new instance of PullRequest
|
||||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||||
return &PullRequest{
|
return &PullRequest{
|
||||||
GitServices: getServices(),
|
GitServices: getServices(gitCommand.Config),
|
||||||
GitCommand: gitCommand,
|
GitCommand: gitCommand,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +122,7 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
|||||||
|
|
||||||
if isHTTP {
|
if isHTTP {
|
||||||
splits := strings.Split(url, "/")
|
splits := strings.Split(url, "/")
|
||||||
owner := splits[len(splits)-2]
|
owner := strings.Join(splits[3:len(splits)-1], "/")
|
||||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||||
|
|
||||||
return &RepoInformation{
|
return &RepoInformation{
|
||||||
@@ -96,8 +133,8 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
|||||||
|
|
||||||
tmpSplit := strings.Split(url, ":")
|
tmpSplit := strings.Split(url, ":")
|
||||||
splits := strings.Split(tmpSplit[1], "/")
|
splits := strings.Split(tmpSplit[1], "/")
|
||||||
owner := splits[0]
|
owner := strings.Join(splits[0:len(splits)-1], "/")
|
||||||
repo := strings.TrimSuffix(splits[1], ".git")
|
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||||
|
|
||||||
return &RepoInformation{
|
return &RepoInformation{
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
|
|||||||
@@ -147,6 +147,13 @@ func TestCreatePullRequest(t *testing.T) {
|
|||||||
gitCommand := NewDummyGitCommand()
|
gitCommand := NewDummyGitCommand()
|
||||||
gitCommand.OSCommand.command = s.command
|
gitCommand.OSCommand.command = s.command
|
||||||
gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
|
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)
|
dummyPullRequest := NewPullRequest(gitCommand)
|
||||||
s.test(dummyPullRequest.Create(s.branch))
|
s.test(dummyPullRequest.Create(s.branch))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Remote Branch : A git remote branch
|
// Remote Branch : A git remote branch
|
||||||
type RemoteBranch struct {
|
type RemoteBranch struct {
|
||||||
Name string
|
Name string
|
||||||
Selected bool
|
|
||||||
RemoteName string
|
RemoteName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings returns the display string of branch
|
|
||||||
func (b *RemoteBranch) GetDisplayStrings(isFocused bool) []string {
|
|
||||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
|
||||||
|
|
||||||
return []string{displayName}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,3 @@ package commands
|
|||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings returns the display string of a remote
|
|
||||||
func (r *Tag) GetDisplayStrings(isFocused bool) []string {
|
|
||||||
return []string{r.Name}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -244,10 +244,11 @@ func GetDefaultConfig() []byte {
|
|||||||
scrollPastBottom: true
|
scrollPastBottom: true
|
||||||
mouseEvents: true
|
mouseEvents: true
|
||||||
skipUnstageLineWarning: false
|
skipUnstageLineWarning: false
|
||||||
|
sidePanelWidth: 0.3333
|
||||||
theme:
|
theme:
|
||||||
lightTheme: false
|
lightTheme: false
|
||||||
activeBorderColor:
|
activeBorderColor:
|
||||||
- white
|
- green
|
||||||
- bold
|
- bold
|
||||||
inactiveBorderColor:
|
inactiveBorderColor:
|
||||||
- white
|
- white
|
||||||
@@ -258,6 +259,9 @@ func GetDefaultConfig() []byte {
|
|||||||
commitLength:
|
commitLength:
|
||||||
show: true
|
show: true
|
||||||
git:
|
git:
|
||||||
|
paging:
|
||||||
|
colorArg: always
|
||||||
|
useConfig: false
|
||||||
merging:
|
merging:
|
||||||
manualCommit: false
|
manualCommit: false
|
||||||
skipHookPrefix: 'WIP'
|
skipHookPrefix: 'WIP'
|
||||||
@@ -308,6 +312,10 @@ keybinding:
|
|||||||
createPatchOptionsMenu: '<c-p>'
|
createPatchOptionsMenu: '<c-p>'
|
||||||
nextTab: ']'
|
nextTab: ']'
|
||||||
prevTab: '['
|
prevTab: '['
|
||||||
|
nextScreenMode: '+'
|
||||||
|
prevScreenMode: '_'
|
||||||
|
undo: 'z'
|
||||||
|
redo: '<c-z>'
|
||||||
status:
|
status:
|
||||||
checkForUpdate: 'u'
|
checkForUpdate: 'u'
|
||||||
recentRepos: '<enter>'
|
recentRepos: '<enter>'
|
||||||
@@ -328,6 +336,7 @@ keybinding:
|
|||||||
checkoutBranchByName: 'c'
|
checkoutBranchByName: 'c'
|
||||||
forceCheckoutBranch: 'F'
|
forceCheckoutBranch: 'F'
|
||||||
rebaseBranch: 'r'
|
rebaseBranch: 'r'
|
||||||
|
renameBranch: 'R'
|
||||||
mergeIntoCurrentBranch: 'M'
|
mergeIntoCurrentBranch: 'M'
|
||||||
viewGitFlowOptions: 'i'
|
viewGitFlowOptions: 'i'
|
||||||
fastForward: 'f'
|
fastForward: 'f'
|
||||||
@@ -353,6 +362,7 @@ keybinding:
|
|||||||
tagCommit: 'T'
|
tagCommit: 'T'
|
||||||
toggleDiffCommit: 'i'
|
toggleDiffCommit: 'i'
|
||||||
checkoutCommit: '<space>'
|
checkoutCommit: '<space>'
|
||||||
|
resetCherryPick: '<c-R>'
|
||||||
stash:
|
stash:
|
||||||
popStash: 'g'
|
popStash: 'g'
|
||||||
commitFiles:
|
commitFiles:
|
||||||
@@ -362,7 +372,6 @@ keybinding:
|
|||||||
toggleDragSelect-alt: 'V'
|
toggleDragSelect-alt: 'V'
|
||||||
toggleSelectHunk: 'a'
|
toggleSelectHunk: 'a'
|
||||||
pickBothHunks: 'b'
|
pickBothHunks: 'b'
|
||||||
undo: 'z'
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
|||||||
if appStatus == "" {
|
if appStatus == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
|
gui.renderString(gui.g, "appStatus", appStatus)
|
||||||
gui.Log.Warn(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
+123
-81
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,12 +40,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
return gui.newStringTask("main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||||
}
|
}
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.Branches.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
|
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
|
||||||
@@ -55,28 +51,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
|
||||||
return gui.newTask("branches", func(stop chan struct{}) error {
|
|
||||||
// here we tell the selected branch that it is selected.
|
|
||||||
// this is necessary for showing stats on a branch that is selected, because
|
|
||||||
// the displaystring function doesn't have access to gui state to tell if it's selected
|
|
||||||
for i, branch := range gui.State.Branches {
|
|
||||||
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
|
|
||||||
}
|
|
||||||
|
|
||||||
branch := gui.getSelectedBranch()
|
|
||||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// gui.refreshStatus is called at the end of this because that's when we can
|
// 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
|
// be sure there is a state.Branches array to pick the current branch from
|
||||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||||
@@ -111,9 +85,8 @@ func (gui *Gui) renderLocalBranchesWithSelection() error {
|
|||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||||
return err
|
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == branchesView {
|
if gui.g.CurrentView() == branchesView {
|
||||||
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -133,7 +106,7 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
|
||||||
}
|
}
|
||||||
branch := gui.getSelectedBranch()
|
branch := gui.getSelectedBranch()
|
||||||
return gui.handleCheckoutRef(branch.Name)
|
return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -163,55 +136,75 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
|||||||
message := gui.Tr.SLocalize("SureForceCheckout")
|
message := gui.Tr.SLocalize("SureForceCheckout")
|
||||||
title := gui.Tr.SLocalize("ForceCheckoutBranch")
|
title := gui.Tr.SLocalize("ForceCheckoutBranch")
|
||||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
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 {
|
if err := gui.GitCommand.Checkout(branch.Name, commands.CheckoutOptions{Force: true}); err != nil {
|
||||||
gui.createErrorPanel(g, err.Error())
|
_ = gui.createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
return gui.refreshSidePanels(g)
|
return gui.refreshSidePanels(g)
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCheckoutRef(ref string) error {
|
type handleCheckoutRefOptions struct {
|
||||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
WaitingStatus string
|
||||||
// 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
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
|
func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error {
|
||||||
// offer to autostash changes
|
waitingStatus := options.WaitingStatus
|
||||||
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 waitingStatus == "" {
|
||||||
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + ref); err != nil {
|
waitingStatus = gui.Tr.SLocalize("CheckingOutStatus")
|
||||||
return gui.createErrorPanel(g, err.Error())
|
|
||||||
}
|
|
||||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
|
||||||
return gui.createErrorPanel(g, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkout successful so we select the new branch
|
|
||||||
gui.State.Panels.Branches.SelectedLine = 0
|
|
||||||
|
|
||||||
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
|
|
||||||
if err := gui.refreshSidePanels(g); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.createErrorPanel(g, err.Error())
|
|
||||||
}
|
|
||||||
return gui.refreshSidePanels(g)
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gui.createErrorPanel(gui.g, err.Error()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Panels.Branches.SelectedLine = 0
|
cmdOptions := commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
|
||||||
gui.State.Panels.Commits.SelectedLine = 0
|
|
||||||
return gui.refreshSidePanels(gui.g)
|
onSuccess := func() {
|
||||||
|
gui.State.Panels.Branches.SelectedLine = 0
|
||||||
|
gui.State.Panels.Commits.SelectedLine = 0
|
||||||
|
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||||
|
gui.State.Panels.Commits.LimitCommits = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.WithWaitingStatus(waitingStatus, func() error {
|
||||||
|
if err := gui.GitCommand.Checkout(ref, cmdOptions); 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 {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := gui.GitCommand.Checkout(ref, cmdOptions); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess()
|
||||||
|
|
||||||
|
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
|
||||||
|
if err := gui.refreshSidePanels(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshSidePanels(g)
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.createErrorPanel(gui.g, err.Error()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess()
|
||||||
|
|
||||||
|
return gui.refreshSidePanels(gui.g)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.handleCheckoutRef(gui.trimmedContent(v))
|
return gui.handleCheckoutRef(gui.trimmedContent(v), handleCheckoutRefOptions{})
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||||
@@ -223,31 +216,31 @@ func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
branch := gui.getCheckedOutBranch()
|
branch := gui.getSelectedBranch()
|
||||||
|
if branch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
message := gui.Tr.TemplateLocalize(
|
message := gui.Tr.TemplateLocalize(
|
||||||
"NewBranchNameBranchOff",
|
"NewBranchNameBranchOff",
|
||||||
Teml{
|
Teml{
|
||||||
"branchName": branch.Name,
|
"branchName": branch.Name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
gui.createPromptPanel(g, v, message, "", func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createPromptPanel(g, v, message, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||||
if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil {
|
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 {
|
||||||
return gui.createErrorPanel(g, err.Error())
|
return gui.createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
gui.refreshSidePanels(g)
|
|
||||||
return gui.handleBranchSelect(g, v)
|
return gui.handleBranchSelect(g, v)
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.deleteBranch(g, v, false)
|
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 {
|
func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||||
selectedBranch := gui.getSelectedBranch()
|
selectedBranch := gui.getSelectedBranch()
|
||||||
if selectedBranch == nil {
|
if selectedBranch == nil {
|
||||||
@@ -381,7 +374,7 @@ func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
if err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName); err != nil {
|
||||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
_ = gui.refreshBranches(gui.g)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||||
@@ -400,7 +393,9 @@ func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
|||||||
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
branchesView.Context = context
|
branchesView.Context = context
|
||||||
gui.onSearchEscape()
|
if err := gui.onSearchEscape(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
contextTabIndexMap := map[string]int{
|
contextTabIndexMap := map[string]int{
|
||||||
"local-branches": 0,
|
"local-branches": 0,
|
||||||
@@ -411,7 +406,13 @@ func (gui *Gui) switchBranchesPanelContext(context string) error {
|
|||||||
|
|
||||||
branchesView.TabIndex = contextTabIndexMap[context]
|
branchesView.TabIndex = contextTabIndexMap[context]
|
||||||
|
|
||||||
switch context {
|
return gui.refreshBranchesViewWithSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) refreshBranchesViewWithSelection() error {
|
||||||
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
|
switch branchesView.Context {
|
||||||
case "local-branches":
|
case "local-branches":
|
||||||
return gui.renderLocalBranchesWithSelection()
|
return gui.renderLocalBranchesWithSelection()
|
||||||
case "remotes":
|
case "remotes":
|
||||||
@@ -461,3 +462,44 @@ func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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, commands.CheckoutOptions{Force: 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 {
|
||||||
|
if len(gui.State.Branches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gui.State.Branches[0]
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
|
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
|
||||||
@@ -35,21 +36,20 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
commitFile := gui.getSelectedCommitFile(g)
|
commitFile := gui.getSelectedCommitFile(g)
|
||||||
if commitFile == nil {
|
if commitFile == nil {
|
||||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.CommitFiles.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
||||||
)
|
)
|
||||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ func (gui *Gui) refreshCommitFilesView() error {
|
|||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
|
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
|
||||||
|
|
||||||
if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil {
|
commitsFileView := gui.getCommitFilesView()
|
||||||
return err
|
displayStrings := presentation.GetCommitFileListDisplayStrings(gui.State.CommitFiles)
|
||||||
}
|
gui.renderDisplayStrings(commitsFileView, displayStrings)
|
||||||
|
|
||||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
return gui.handleCommitFileSelect(gui.g, commitsFileView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -131,7 +131,8 @@ func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
commitFile := gui.getSelectedCommitFile(g)
|
commitFile := gui.getSelectedCommitFile(g)
|
||||||
if commitFile == nil {
|
if commitFile == nil {
|
||||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTheFile := func() error {
|
toggleTheFile := func() error {
|
||||||
@@ -186,7 +187,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
|||||||
|
|
||||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||||
if commitFile == nil {
|
if commitFile == nil {
|
||||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enterTheFile := func(selectedLineIdx int) error {
|
enterTheFile := func(selectedLineIdx int) error {
|
||||||
|
|||||||
@@ -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 {
|
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
|
||||||
g.SetViewOnBottom("commitMessage")
|
_, _ = g.SetViewOnBottom("commitMessage")
|
||||||
return gui.switchFocus(g, v, gui.getFilesView())
|
return gui.switchFocus(g, v, gui.getFilesView())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,8 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
|||||||
"keyBindConfirm": "enter",
|
"keyBindConfirm": "enter",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return gui.renderString(g, "options", message)
|
gui.renderString(g, "options", message)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
||||||
|
|||||||
+40
-73
@@ -3,10 +3,9 @@ package gui
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state := gui.State.Panels.Commits
|
state := gui.State.Panels.Commits
|
||||||
if state.SelectedLine > 20 && state.LimitCommits {
|
if state.SelectedLine > 290 && state.LimitCommits {
|
||||||
state.LimitCommits = false
|
state.LimitCommits = false
|
||||||
go func() {
|
go func() {
|
||||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||||
@@ -54,9 +53,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
return gui.newStringTask("main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.Commits.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if specific diff mode is on, don't show diff
|
// if specific diff mode is on, don't show diff
|
||||||
if gui.State.Panels.Commits.SpecificDiffMode {
|
if gui.State.Panels.Commits.SpecificDiffMode {
|
||||||
@@ -66,7 +63,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||||
)
|
)
|
||||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +73,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||||
g.Update(func(*gocui.Gui) error {
|
g.Update(func(*gocui.Gui) error {
|
||||||
// I think this is here for the sake of some kind of rebasing thing
|
// I think this is here for the sake of some kind of rebasing thing
|
||||||
gui.refreshStatus(g)
|
_ = gui.refreshStatus(g)
|
||||||
|
|
||||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -120,28 +117,6 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
|||||||
|
|
||||||
// specific functions
|
// 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 {
|
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||||
if len(gui.State.Commits) <= 1 {
|
if len(gui.State.Commits) <= 1 {
|
||||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||||
@@ -155,23 +130,12 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Squash"), gui.Tr.SLocalize("SureSquashThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
return 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 {
|
return gui.WithWaitingStatus(gui.Tr.SLocalize("SquashingStatus"), func() error {
|
||||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
})
|
})
|
||||||
}, nil)
|
}, 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 {
|
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -187,13 +151,12 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Fixup"), gui.Tr.SLocalize("SureFixupThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
return 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 {
|
return gui.WithWaitingStatus(gui.Tr.SLocalize("FixingStatus"), func() error {
|
||||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
||||||
return gui.handleGenericMergeCommandResult(err)
|
return gui.handleGenericMergeCommandResult(err)
|
||||||
})
|
})
|
||||||
}, nil)
|
}, nil)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -263,21 +226,6 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
|||||||
return true, gui.refreshCommits(gui.g)
|
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 {
|
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||||
applied, err := gui.handleMidRebaseCommand("drop")
|
applied, err := gui.handleMidRebaseCommand("drop")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -403,14 +351,21 @@ func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.refreshCommits(gui.g)
|
return gui.refreshCommits(gui.g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) cherryPickedCommitShaMap() map[string]bool {
|
||||||
|
commitShaMap := map[string]bool{}
|
||||||
|
for _, commit := range gui.State.CherryPickedCommits {
|
||||||
|
commitShaMap[commit.Sha] = true
|
||||||
|
}
|
||||||
|
return commitShaMap
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
||||||
// not super happy with modifying the state of the Commits array here
|
commitShaMap := gui.cherryPickedCommitShaMap()
|
||||||
// but the alternative would be very tricky
|
commitShaMap[gui.State.Commits[index].Sha] = true
|
||||||
gui.State.Commits[index].Copied = true
|
|
||||||
|
|
||||||
newCommits := []*commands.Commit{}
|
newCommits := []*commands.Commit{}
|
||||||
for _, commit := range gui.State.Commits {
|
for _, commit := range gui.State.Commits {
|
||||||
if commit.Copied {
|
if commitShaMap[commit.Sha] {
|
||||||
// duplicating just the things we need to put in the rebase TODO list
|
// duplicating just the things we need to put in the rebase TODO list
|
||||||
newCommits = append(newCommits, &commands.Commit{Name: commit.Name, Sha: commit.Sha})
|
newCommits = append(newCommits, &commands.Commit{Name: commit.Name, Sha: commit.Sha})
|
||||||
}
|
}
|
||||||
@@ -420,13 +375,13 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
||||||
// whenever I add a commit, I need to make sure I retain its order
|
commitShaMap := gui.cherryPickedCommitShaMap()
|
||||||
|
|
||||||
// find the last commit that is copied that's above our position
|
// find the last commit that is copied that's above our position
|
||||||
// if there are none, startIndex = 0
|
// if there are none, startIndex = 0
|
||||||
startIndex := 0
|
startIndex := 0
|
||||||
for index, commit := range gui.State.Commits[0:gui.State.Panels.Commits.SelectedLine] {
|
for index, commit := range gui.State.Commits[0:gui.State.Panels.Commits.SelectedLine] {
|
||||||
if commit.Copied {
|
if commitShaMap[commit.Sha] {
|
||||||
startIndex = index
|
startIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,7 +392,7 @@ func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
|||||||
gui.addCommitToCherryPickedCommits(index)
|
gui.addCommitToCherryPickedCommits(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.refreshCommits(gui.g)
|
return gui.renderBranchCommitsWithSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
||||||
@@ -503,7 +458,7 @@ func (gui *Gui) setDiffMode() {
|
|||||||
v.Title = gui.Tr.SLocalize("CommitsTitle")
|
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) {
|
func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) {
|
||||||
@@ -592,7 +547,7 @@ func (gui *Gui) handleCheckoutCommit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.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)
|
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,9 +555,8 @@ func (gui *Gui) renderBranchCommitsWithSelection() error {
|
|||||||
commitsView := gui.getCommitsView()
|
commitsView := gui.getCommitsView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||||
if err := gui.renderListPanel(commitsView, gui.State.Commits); err != nil {
|
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap())
|
||||||
return err
|
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
||||||
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -623,7 +577,9 @@ func (gui *Gui) onCommitsTabClick(tabIndex int) error {
|
|||||||
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||||
commitsView := gui.getCommitsView()
|
commitsView := gui.getCommitsView()
|
||||||
commitsView.Context = context
|
commitsView.Context = context
|
||||||
gui.onSearchEscape()
|
if err := gui.onSearchEscape(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
contextTabIndexMap := map[string]int{
|
contextTabIndexMap := map[string]int{
|
||||||
"branch-commits": 0,
|
"branch-commits": 0,
|
||||||
@@ -632,7 +588,13 @@ func (gui *Gui) switchCommitsPanelContext(context string) error {
|
|||||||
|
|
||||||
commitsView.TabIndex = contextTabIndexMap[context]
|
commitsView.TabIndex = contextTabIndexMap[context]
|
||||||
|
|
||||||
switch context {
|
return gui.refreshCommitsViewWithSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) refreshCommitsViewWithSelection() error {
|
||||||
|
commitsView := gui.getCommitsView()
|
||||||
|
|
||||||
|
switch commitsView.Context {
|
||||||
case "branch-commits":
|
case "branch-commits":
|
||||||
return gui.renderBranchCommitsWithSelection()
|
return gui.renderBranchCommitsWithSelection()
|
||||||
case "reflog-commits":
|
case "reflog-commits":
|
||||||
@@ -687,3 +649,8 @@ func (gui *Gui) handleOpenSearchForCommitsPanel(g *gocui.Gui, v *gocui.View) err
|
|||||||
|
|
||||||
return gui.handleOpenSearch(gui.g, v)
|
return gui.handleOpenSearch(gui.g, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleResetCherryPick(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
gui.State.CherryPickedCommits = []*commands.Commit{}
|
||||||
|
return gui.refreshCommits(gui.g)
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui, returnFocusOnClose bool) e
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.DeleteKeybindings("confirmation")
|
g.DeleteKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone)
|
||||||
|
g.DeleteKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone)
|
||||||
return g.DeleteView("confirmation")
|
return g.DeleteView("confirmation")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,9 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt s
|
|||||||
width, height := g.Size()
|
width, height := g.Size()
|
||||||
panelWidth := 4 * width / 7
|
panelWidth := 4 * width / 7
|
||||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||||
|
if panelHeight > height*3/4 {
|
||||||
|
panelHeight = height * 3 / 4
|
||||||
|
}
|
||||||
return width/2 - panelWidth/2,
|
return width/2 - panelWidth/2,
|
||||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||||
width/2 + panelWidth/2,
|
width/2 + panelWidth/2,
|
||||||
@@ -122,9 +126,7 @@ func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, p
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
gui.renderString(g, "confirmation", prompt)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
|
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -151,19 +153,14 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
|||||||
"keyBindConfirm": "enter",
|
"keyBindConfirm": "enter",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err := gui.renderString(g, "options", actions); err != nil {
|
|
||||||
return err
|
gui.renderString(g, "options", actions)
|
||||||
}
|
|
||||||
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
if err := g.SetKeybinding("confirmation", nil, gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
return g.SetKeybinding("confirmation", nil, gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
|
||||||
return gui.createPopupPanel(g, currentView, title, prompt, false, true, false, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||||
// view to be focused when the user closes the popup, and a boolean specifying
|
// view to be focused when the user closes the popup, and a boolean specifying
|
||||||
// whether we will log the error. If the message may include a user password,
|
// whether we will log the error. If the message may include a user password,
|
||||||
|
|||||||
@@ -16,5 +16,4 @@ func (gui *Gui) changeMainViewsContext(context string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.State.MainContext = context
|
gui.State.MainContext = context
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error
|
|||||||
"keyBindConfirm": "enter",
|
"keyBindConfirm": "enter",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return gui.renderString(g, "options", message)
|
gui.renderString(g, "options", message)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials
|
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ type fileWatcher struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFileWatcher(log *logrus.Entry) *fileWatcher {
|
func NewFileWatcher(log *logrus.Entry) *fileWatcher {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
// TODO: get this going again, and ensure we don't see any crashes from it
|
||||||
log.Error(err)
|
|
||||||
return &fileWatcher{
|
return &fileWatcher{
|
||||||
Disabled: true,
|
Disabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return &fileWatcher{
|
return &fileWatcher{
|
||||||
|
|||||||
+29
-47
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@@ -37,9 +37,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
|||||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoChangedFiles"))
|
return gui.newStringTask("main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), gui.getFilesView()); err != nil {
|
gui.getFilesView().FocusPoint(0, gui.State.Panels.Files.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.HasInlineMergeConflicts {
|
if file.HasInlineMergeConflicts {
|
||||||
gui.getMainView().Title = gui.Tr.SLocalize("MergeConflictsTitle")
|
gui.getMainView().Title = gui.Tr.SLocalize("MergeConflictsTitle")
|
||||||
@@ -62,7 +60,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
|||||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, true)
|
cmdStr := gui.GitCommand.DiffCmdStr(file, false, true)
|
||||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||||
if err := gui.newCmdTask("secondary", cmd); err != nil {
|
if err := gui.newPtyTask("secondary", cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -76,7 +74,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
|||||||
|
|
||||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,14 +101,8 @@ func (gui *Gui) refreshFiles() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
|
displayStrings := presentation.GetFileListDisplayStrings(gui.State.Files)
|
||||||
filesView.Clear()
|
gui.renderDisplayStrings(filesView, displayStrings)
|
||||||
isFocused := gui.g.CurrentView().Name() == "files"
|
|
||||||
list, err := utils.RenderList(gui.State.Files, isFocused)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprint(filesView, list)
|
|
||||||
|
|
||||||
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == "merging") {
|
||||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||||
@@ -194,9 +186,12 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if file.HasUnstagedChanges {
|
if file.HasUnstagedChanges {
|
||||||
gui.GitCommand.StageFile(file.Name)
|
err = gui.GitCommand.StageFile(file.Name)
|
||||||
} else {
|
} else {
|
||||||
gui.GitCommand.UnStageFile(file.Name, file.Tracked)
|
err = gui.GitCommand.UnStageFile(file.Name, file.Tracked)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.refreshFiles(); err != nil {
|
if err := gui.refreshFiles(); err != nil {
|
||||||
@@ -274,9 +269,7 @@ func (gui *Gui) handleWIPCommitPress(g *gocui.Gui, filesView *gocui.View) error
|
|||||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.renderString(g, "commitMessage", skipHookPreifx); err != nil {
|
gui.renderString(g, "commitMessage", skipHookPreifx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
|
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -290,8 +283,14 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
|||||||
}
|
}
|
||||||
commitMessageView := gui.getCommitMessageView()
|
commitMessageView := gui.getCommitMessageView()
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
g.SetViewOnTop("commitMessage")
|
if _, err := g.SetViewOnTop("commitMessage"); err != nil {
|
||||||
gui.switchFocus(g, filesView, commitMessageView)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.switchFocus(g, filesView, commitMessageView); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
gui.RenderCommitLength()
|
gui.RenderCommitLength()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -400,24 +399,20 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
|||||||
|
|
||||||
func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||||
// if we have no upstream branch we need to set that first
|
// if we have no upstream branch we need to set that first
|
||||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
currentBranch := gui.currentBranch()
|
||||||
currentBranchName, err := gui.GitCommand.CurrentBranchName()
|
if currentBranch.Pullables == "?" {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pullables == "?" {
|
|
||||||
// see if we have this branch in our config with an upstream
|
// see if we have this branch in our config with an upstream
|
||||||
conf, err := gui.GitCommand.Repo.Config()
|
conf, err := gui.GitCommand.Repo.Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
for branchName, branch := range conf.Branches {
|
for branchName, branch := range conf.Branches {
|
||||||
if branchName == currentBranchName {
|
if branchName == currentBranch.Name {
|
||||||
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName))
|
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranchName, func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+currentBranch.Name, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
upstream := gui.trimmedContent(v)
|
upstream := gui.trimmedContent(v)
|
||||||
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
|
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
|
||||||
errorMessage := err.Error()
|
errorMessage := err.Error()
|
||||||
@@ -468,28 +463,24 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstr
|
|||||||
|
|
||||||
func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
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
|
// if we have pullables we'll ask if the user wants to force push
|
||||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
currentBranch := gui.currentBranch()
|
||||||
currentBranchName, err := gui.GitCommand.CurrentBranchName()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pullables == "?" {
|
if currentBranch.Pullables == "?" {
|
||||||
// see if we have this branch in our config with an upstream
|
// see if we have this branch in our config with an upstream
|
||||||
conf, err := gui.GitCommand.Repo.Config()
|
conf, err := gui.GitCommand.Repo.Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
for branchName, branch := range conf.Branches {
|
for branchName, branch := range conf.Branches {
|
||||||
if branchName == currentBranchName {
|
if branchName == currentBranch.Name {
|
||||||
return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName))
|
return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+currentBranchName, func(g *gocui.Gui, v *gocui.View) error {
|
return gui.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), "")
|
return gui.pushWithForceFlag(g, v, false, gui.trimmedContent(v), "")
|
||||||
})
|
})
|
||||||
} else if pullables == "0" {
|
} else if currentBranch.Pullables == "0" {
|
||||||
return gui.pushWithForceFlag(g, v, false, "", "")
|
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.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
@@ -515,15 +506,6 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.refreshMergePanel()
|
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 {
|
func (gui *Gui) openFile(filename string) error {
|
||||||
if err := gui.OSCommand.OpenFile(filename); err != nil {
|
if err := gui.OSCommand.OpenFile(filename); err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
|||||||
+192
-84
@@ -33,6 +33,12 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SCREEN_NORMAL int = iota
|
||||||
|
SCREEN_HALF
|
||||||
|
SCREEN_FULL
|
||||||
|
)
|
||||||
|
|
||||||
const StartupPopupVersion = 1
|
const StartupPopupVersion = 1
|
||||||
|
|
||||||
// OverlappingEdges determines if panel edges overlap
|
// OverlappingEdges determines if panel edges overlap
|
||||||
@@ -74,7 +80,7 @@ type Gui struct {
|
|||||||
GitCommand *commands.GitCommand
|
GitCommand *commands.GitCommand
|
||||||
OSCommand *commands.OSCommand
|
OSCommand *commands.OSCommand
|
||||||
SubProcess *exec.Cmd
|
SubProcess *exec.Cmd
|
||||||
State guiState
|
State *guiState
|
||||||
Config config.AppConfigurer
|
Config config.AppConfigurer
|
||||||
Tr *i18n.Localizer
|
Tr *i18n.Localizer
|
||||||
Errors SentinelErrors
|
Errors SentinelErrors
|
||||||
@@ -202,6 +208,12 @@ type guiState struct {
|
|||||||
IsRefreshingFiles bool
|
IsRefreshingFiles bool
|
||||||
RefreshingFilesMutex sync.Mutex
|
RefreshingFilesMutex sync.Mutex
|
||||||
Searching searchingState
|
Searching searchingState
|
||||||
|
ScreenMode int
|
||||||
|
SideView *gocui.View
|
||||||
|
Ptmx *os.File
|
||||||
|
PrevMainWidth int
|
||||||
|
PrevMainHeight int
|
||||||
|
OldInformation string
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the split view will always be on
|
// for now the split view will always be on
|
||||||
@@ -209,7 +221,7 @@ type guiState struct {
|
|||||||
// NewGui builds a new gui handler
|
// 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) {
|
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),
|
Files: make([]*commands.File, 0),
|
||||||
PreviousView: "files",
|
PreviousView: "files",
|
||||||
Commits: make([]*commands.Commit, 0),
|
Commits: make([]*commands.Commit, 0),
|
||||||
@@ -236,6 +248,9 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
|||||||
},
|
},
|
||||||
Status: &statusPanelState{},
|
Status: &statusPanelState{},
|
||||||
},
|
},
|
||||||
|
ScreenMode: SCREEN_NORMAL,
|
||||||
|
SideView: nil,
|
||||||
|
Ptmx: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
gui := &Gui{
|
gui := &Gui{
|
||||||
@@ -257,6 +272,34 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
|||||||
return gui, nil
|
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 {
|
func (gui *Gui) scrollUpView(viewName string) error {
|
||||||
mainView, _ := gui.g.View(viewName)
|
mainView, _ := gui.g.View(viewName)
|
||||||
ox, oy := mainView.Origin()
|
ox, oy := mainView.Origin()
|
||||||
@@ -300,6 +343,20 @@ func (gui *Gui) scrollDownSecondary(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.scrollDownView("secondary")
|
return gui.scrollDownView("secondary")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) scrollUpConfirmationPanel(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if v.Editable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gui.scrollUpView("confirmation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) scrollDownConfirmationPanel(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
if v.Editable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return gui.scrollDownView("confirmation")
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.refreshSidePanels(g)
|
return gui.refreshSidePanels(g)
|
||||||
}
|
}
|
||||||
@@ -346,17 +403,11 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if v.IsSearching() && newView.Name() != "search" {
|
if v.IsSearching() && newView.Name() != "search" {
|
||||||
gui.onSearchEscape()
|
if err := gui.onSearchEscape(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
switch v.Name() {
|
switch v.Name() {
|
||||||
case "branches":
|
|
||||||
if v.Context == "local-branches" {
|
|
||||||
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
|
||||||
// inside renderListPanel it checks to see if the panel has focus
|
|
||||||
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "main":
|
case "main":
|
||||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||||
gui.changeMainViewsContext("normal")
|
gui.changeMainViewsContext("normal")
|
||||||
@@ -379,6 +430,75 @@ func (gui *Gui) onFocus(v *gocui.View) error {
|
|||||||
return nil
|
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
|
// layout is called for every screen re-render e.g. when the screen is resized
|
||||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||||
g.Highlight = true
|
g.Highlight = true
|
||||||
@@ -389,6 +509,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
|
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
|
||||||
information = donate + " " + information
|
information = donate + " " + information
|
||||||
}
|
}
|
||||||
|
if len(gui.State.CherryPickedCommits) > 0 {
|
||||||
|
information = utils.ColoredString(fmt.Sprintf("%d commits copied", len(gui.State.CherryPickedCommits)), color.FgCyan)
|
||||||
|
}
|
||||||
|
|
||||||
minimumHeight := 9
|
minimumHeight := 9
|
||||||
minimumWidth := 10
|
minimumWidth := 10
|
||||||
@@ -405,50 +528,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
currView := gui.g.CurrentView()
|
vHeights := gui.getViewHeights()
|
||||||
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)
|
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||||
|
|
||||||
@@ -458,30 +538,47 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
appStatusOptionsBoundary = len(appStatus) + 2
|
appStatusOptionsBoundary = len(appStatus) + 2
|
||||||
}
|
}
|
||||||
|
|
||||||
panelSpacing := 1
|
|
||||||
if OverlappingEdges {
|
|
||||||
panelSpacing = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = g.SetViewOnBottom("limit")
|
_, _ = g.SetViewOnBottom("limit")
|
||||||
g.DeleteView("limit")
|
_ = g.DeleteView("limit")
|
||||||
|
|
||||||
|
sidePanelWidthRatio := gui.Config.GetUserConfig().GetFloat64("gui.sidePanelWidth")
|
||||||
|
|
||||||
textColor := theme.GocuiDefaultTextColor
|
textColor := theme.GocuiDefaultTextColor
|
||||||
leftSideWidth := width / 3
|
var leftSideWidth int
|
||||||
panelSplitX := width - 1
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPanelLeft := leftSideWidth + 1
|
||||||
mainPanelRight := width - 1
|
mainPanelRight := width - 1
|
||||||
secondaryPanelLeft := width - 1
|
secondaryPanelLeft := width - 1
|
||||||
secondaryPanelTop := 0
|
secondaryPanelTop := 0
|
||||||
mainPanelBottom := height - 2
|
mainPanelBottom := height - 2
|
||||||
if gui.State.SplitMainPanel {
|
if gui.State.SplitMainPanel {
|
||||||
if width < 220 {
|
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
|
mainPanelBottom = height/2 - 1
|
||||||
secondaryPanelTop = mainPanelBottom + 1
|
secondaryPanelTop = mainPanelBottom + 1
|
||||||
secondaryPanelLeft = leftSideWidth + 1
|
secondaryPanelLeft = leftSideWidth + 1
|
||||||
} else {
|
} else {
|
||||||
units := 5
|
units := 5
|
||||||
leftSideWidth = width / units
|
leftSideWidth = width / units
|
||||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
mainPanelLeft = leftSideWidth + 1
|
||||||
|
panelSplitX := (1 + ((units - 1) / 2)) * width / units
|
||||||
mainPanelRight = panelSplitX
|
mainPanelRight = panelSplitX
|
||||||
secondaryPanelLeft = panelSplitX + 1
|
secondaryPanelLeft = panelSplitX + 1
|
||||||
}
|
}
|
||||||
@@ -510,7 +607,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
v, err := g.SetView(main, mainPanelLeft, 0, mainPanelRight, mainPanelBottom, gocui.LEFT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
@@ -519,16 +616,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
v.Wrap = true
|
v.Wrap = true
|
||||||
v.FgColor = textColor
|
v.FgColor = textColor
|
||||||
v.IgnoreCarriageReturns = true
|
v.IgnoreCarriageReturns = true
|
||||||
v.SetOnSelectItem(gui.onSelectItemWrapper(func(selectedLine int) error {
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenViewOffset := 0
|
hiddenViewOffset := 9999
|
||||||
|
|
||||||
|
hiddenSecondaryPanelOffset := 0
|
||||||
if !gui.State.SplitMainPanel {
|
if !gui.State.SplitMainPanel {
|
||||||
hiddenViewOffset = 9999
|
hiddenSecondaryPanelOffset = hiddenViewOffset
|
||||||
}
|
}
|
||||||
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenViewOffset, hiddenViewOffset+secondaryPanelTop, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
secondaryView, err := g.SetView(secondary, secondaryPanelLeft+hiddenSecondaryPanelOffset, hiddenSecondaryPanelOffset+secondaryPanelTop, width-1+hiddenSecondaryPanelOffset, height-2+hiddenSecondaryPanelOffset, gocui.LEFT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
@@ -577,6 +673,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
v.Title = gui.Tr.SLocalize("CommitFiles")
|
v.Title = gui.Tr.SLocalize("CommitFiles")
|
||||||
v.FgColor = textColor
|
v.FgColor = textColor
|
||||||
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
|
v.SetOnSelectItem(gui.onSelectItemWrapper(gui.onCommitFilesPanelSearchSelect))
|
||||||
|
v.ContainsList = true
|
||||||
}
|
}
|
||||||
|
|
||||||
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
||||||
@@ -616,7 +713,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
g.SetViewOnBottom("commitMessage")
|
_, _ = g.SetViewOnBottom("commitMessage")
|
||||||
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
||||||
commitMessageView.FgColor = textColor
|
commitMessageView.FgColor = textColor
|
||||||
commitMessageView.Editable = true
|
commitMessageView.Editable = true
|
||||||
@@ -681,22 +778,25 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
|
informationView, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0)
|
||||||
|
if err != nil {
|
||||||
if err.Error() != "unknown view" {
|
if err.Error() != "unknown view" {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.BgColor = gocui.ColorDefault
|
informationView.BgColor = gocui.ColorDefault
|
||||||
v.FgColor = gocui.ColorGreen
|
informationView.FgColor = gocui.ColorGreen
|
||||||
v.Frame = false
|
informationView.Frame = false
|
||||||
if err := gui.renderString(g, "information", information); err != nil {
|
gui.renderString(g, "information", information)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// doing this here because it'll only happen once
|
// doing this here because it'll only happen once
|
||||||
if err := gui.onInitialViewsCreation(); err != nil {
|
if err := gui.onInitialViewsCreation(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if gui.State.OldInformation != information {
|
||||||
|
gui.setViewContent(g, informationView, information)
|
||||||
|
gui.State.OldInformation = information
|
||||||
|
}
|
||||||
|
|
||||||
if gui.g.CurrentView() == nil {
|
if gui.g.CurrentView() == nil {
|
||||||
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
||||||
@@ -735,7 +835,14 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// check if the selected line is now out of view and if so refocus it
|
// check if the selected line is now out of view and if so refocus it
|
||||||
if err := gui.focusPoint(0, listView.selectedLine, listView.lineCount, listView.view); err != nil {
|
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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -823,7 +930,8 @@ 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.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Error"), coloredMessage, close, close)
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.refreshStatus(g)
|
_ = gui.refreshStatus(g)
|
||||||
|
|
||||||
return unamePassOpend, err
|
return unamePassOpend, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,7 +940,7 @@ func (gui *Gui) renderGlobalOptions() error {
|
|||||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.scrollUpMain"), gui.getKeyDisplay("universal.scrollDownMain")): gui.Tr.SLocalize("scroll"),
|
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 %s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock"), gui.getKeyDisplay("universal.prevItem"), gui.getKeyDisplay("universal.nextItem")): gui.Tr.SLocalize("navigate"),
|
||||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.return"), gui.getKeyDisplay("universal.quit")): gui.Tr.SLocalize("close"),
|
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.return"), gui.getKeyDisplay("universal.quit")): gui.Tr.SLocalize("close"),
|
||||||
fmt.Sprintf("%s", gui.getKeyDisplay("universal.optionMenu")): gui.Tr.SLocalize("menu"),
|
gui.getKeyDisplay("universal.optionMenu"): gui.Tr.SLocalize("menu"),
|
||||||
"1-5": gui.Tr.SLocalize("jump"),
|
"1-5": gui.Tr.SLocalize("jump"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -871,7 +979,7 @@ func (gui *Gui) startBackgroundFetch() {
|
|||||||
|
|
||||||
// Run setup the gui with keybindings and start the mainloop
|
// Run setup the gui with keybindings and start the mainloop
|
||||||
func (gui *Gui) Run() error {
|
func (gui *Gui) Run() error {
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
g, err := gocui.NewGui(gocui.Output256, OverlappingEdges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-50
@@ -162,15 +162,15 @@ func (gui *Gui) getKeyDisplay(name string) string {
|
|||||||
func GetKeyDisplay(key interface{}) string {
|
func GetKeyDisplay(key interface{}) string {
|
||||||
keyInt := 0
|
keyInt := 0
|
||||||
|
|
||||||
switch key.(type) {
|
switch key := key.(type) {
|
||||||
case rune:
|
case rune:
|
||||||
keyInt = int(key.(rune))
|
keyInt = int(key)
|
||||||
case gocui.Key:
|
case gocui.Key:
|
||||||
value, ok := keyMapReversed[key.(gocui.Key)]
|
value, ok := keyMapReversed[key]
|
||||||
if ok {
|
if ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
keyInt = int(key.(gocui.Key))
|
keyInt = int(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(keyInt)
|
return string(keyInt)
|
||||||
@@ -225,6 +225,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.scrollUpMain,
|
Handler: gui.scrollUpMain,
|
||||||
Alternative: "fn+up",
|
Alternative: "fn+up",
|
||||||
|
Description: gui.Tr.SLocalize("scrollUpMainPanel"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
@@ -232,6 +233,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.scrollDownMain,
|
Handler: gui.scrollDownMain,
|
||||||
Alternative: "fn+down",
|
Alternative: "fn+down",
|
||||||
|
Description: gui.Tr.SLocalize("scrollDownMainPanel"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
@@ -293,10 +295,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Description: gui.Tr.SLocalize("refresh"),
|
Description: gui.Tr.SLocalize("refresh"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
Key: gui.getKey("universal.optionMenu"),
|
Key: gui.getKey("universal.optionMenu"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCreateOptionsMenu,
|
Handler: gui.handleCreateOptionsMenu,
|
||||||
|
Description: gui.Tr.SLocalize("openMenu"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "",
|
ViewName: "",
|
||||||
@@ -310,6 +313,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleCreateOptionsMenu,
|
Handler: gui.handleCreateOptionsMenu,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "",
|
||||||
|
Key: gui.getKey("universal.undo"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.reflogUndo,
|
||||||
|
Description: gui.Tr.SLocalize("undoReflog"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "",
|
||||||
|
Key: gui.getKey("universal.redo"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.reflogRedo,
|
||||||
|
Description: gui.Tr.SLocalize("redoReflog"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "status",
|
ViewName: "status",
|
||||||
Key: gui.getKey("universal.edit"),
|
Key: gui.getKey("universal.edit"),
|
||||||
@@ -317,6 +334,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Handler: gui.handleEditConfig,
|
Handler: gui.handleEditConfig,
|
||||||
Description: gui.Tr.SLocalize("EditConfig"),
|
Description: gui.Tr.SLocalize("EditConfig"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "",
|
||||||
|
Key: gui.getKey("universal.nextScreenMode"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.nextScreenMode,
|
||||||
|
Description: gui.Tr.SLocalize("nextScreenMode"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "",
|
||||||
|
Key: gui.getKey("universal.prevScreenMode"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.prevScreenMode,
|
||||||
|
Description: gui.Tr.SLocalize("prevScreenMode"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "status",
|
ViewName: "status",
|
||||||
Key: gui.getKey("universal.openFile"),
|
Key: gui.getKey("universal.openFile"),
|
||||||
@@ -552,6 +583,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Handler: gui.handleCreateResetToBranchMenu,
|
Handler: gui.handleCreateResetToBranchMenu,
|
||||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "branches",
|
||||||
|
Contexts: []string{"local-branches"},
|
||||||
|
Key: gui.getKey("branches.renameBranch"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleRenameBranch,
|
||||||
|
Description: gui.Tr.SLocalize("renameBranch"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
Contexts: []string{"tags"},
|
Contexts: []string{"tags"},
|
||||||
@@ -593,16 +632,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
Key: gui.getKey("universal.nextTab"),
|
Key: gui.getKey("universal.nextTab"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleNextBranchesTab,
|
Handler: gui.handleNextBranchesTab,
|
||||||
|
Description: gui.Tr.SLocalize("nextTab"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
Key: gui.getKey("universal.prevTab"),
|
Key: gui.getKey("universal.prevTab"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handlePrevBranchesTab,
|
Handler: gui.handlePrevBranchesTab,
|
||||||
|
Description: gui.Tr.SLocalize("prevTab"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
@@ -629,22 +670,25 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Description: gui.Tr.SLocalize("fetchRemote"),
|
Description: gui.Tr.SLocalize("fetchRemote"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
Key: gui.getKey("universal.nextTab"),
|
Key: gui.getKey("universal.nextTab"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleNextCommitsTab,
|
Handler: gui.handleNextCommitsTab,
|
||||||
|
Description: gui.Tr.SLocalize("nextTab"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
Key: gui.getKey("universal.prevTab"),
|
Key: gui.getKey("universal.prevTab"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handlePrevCommitsTab,
|
Handler: gui.handlePrevCommitsTab,
|
||||||
|
Description: gui.Tr.SLocalize("prevTab"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
Key: gui.getKey("universal.startSearch"),
|
Key: gui.getKey("universal.startSearch"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleOpenSearchForCommitsPanel,
|
Handler: gui.handleOpenSearchForCommitsPanel,
|
||||||
|
Description: gui.Tr.SLocalize("startSearch"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
@@ -814,6 +858,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Handler: gui.handleTagCommit,
|
Handler: gui.handleTagCommit,
|
||||||
Description: gui.Tr.SLocalize("tagCommit"),
|
Description: gui.Tr.SLocalize("tagCommit"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "commits",
|
||||||
|
Contexts: []string{"branch-commits"},
|
||||||
|
Key: gui.getKey("commits.resetCherryPick"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleResetCherryPick,
|
||||||
|
Description: gui.Tr.SLocalize("resetCherryPick"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "commits",
|
ViewName: "commits",
|
||||||
Contexts: []string{"reflog-commits"},
|
Contexts: []string{"reflog-commits"},
|
||||||
@@ -876,16 +928,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Handler: gui.handleCloseCredentialsView,
|
Handler: gui.handleCloseCredentialsView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: gui.getKey("universal.return"),
|
Key: gui.getKey("universal.return"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleMenuClose,
|
Handler: gui.handleMenuClose,
|
||||||
|
Description: gui.Tr.SLocalize("closeMenu"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "menu",
|
ViewName: "menu",
|
||||||
Key: gui.getKey("universal.quit"),
|
Key: gui.getKey("universal.quit"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleMenuClose,
|
Handler: gui.handleMenuClose,
|
||||||
|
Description: gui.Tr.SLocalize("closeMenu"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "information",
|
ViewName: "information",
|
||||||
@@ -999,7 +1053,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Contexts: []string{"staging"},
|
Contexts: []string{"staging"},
|
||||||
Key: gui.getKey("universal.select"),
|
Key: gui.getKey("universal.select"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleStageSelection,
|
Handler: gui.handleToggleStagedSelection,
|
||||||
Description: gui.Tr.SLocalize("StageSelection"),
|
Description: gui.Tr.SLocalize("StageSelection"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1121,16 +1175,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Contexts: []string{"patch-building"},
|
Contexts: []string{"patch-building"},
|
||||||
Key: gui.getKey("universal.select"),
|
Key: gui.getKey("universal.select"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleAddSelectionToPatch,
|
Handler: gui.handleToggleSelectionForPatch,
|
||||||
Description: gui.Tr.SLocalize("StageSelection"),
|
Description: gui.Tr.SLocalize("ToggleSelectionForPatch"),
|
||||||
},
|
|
||||||
{
|
|
||||||
ViewName: "main",
|
|
||||||
Contexts: []string{"patch-building"},
|
|
||||||
Key: gui.getKey("universal.remove"),
|
|
||||||
Modifier: gocui.ModNone,
|
|
||||||
Handler: gui.handleRemoveSelectionFromPatch,
|
|
||||||
Description: gui.Tr.SLocalize("ResetSelection"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
@@ -1310,10 +1356,10 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{"merging"},
|
Contexts: []string{"merging"},
|
||||||
Key: gui.getKey("main.undo"),
|
Key: gui.getKey("universal.undo"),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handlePopFileSnapshot,
|
Handler: gui.handlePopFileSnapshot,
|
||||||
Description: gui.Tr.SLocalize("Undo"),
|
Description: gui.Tr.SLocalize("undo"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "branches",
|
ViewName: "branches",
|
||||||
@@ -1416,6 +1462,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSearchEscape,
|
Handler: gui.handleSearchEscape,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "confirmation",
|
||||||
|
Key: gui.getKey("universal.prevItem"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.scrollUpConfirmationPanel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "confirmation",
|
||||||
|
Key: gui.getKey("universal.nextItem"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.scrollDownConfirmationPanel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "confirmation",
|
||||||
|
Key: gui.getKey("universal.prevItem-alt"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.scrollUpConfirmationPanel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ViewName: "confirmation",
|
||||||
|
Key: gui.getKey("universal.nextItem-alt"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.scrollDownConfirmationPanel,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
|
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
|
||||||
@@ -1446,7 +1516,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
|
|
||||||
// we need a specific keybinding for the commits panel beacuse it usually lazyloads commits
|
// we need a specific keybinding for the commits panel beacuse it usually lazyloads commits
|
||||||
if listView.viewName != "commits" {
|
if listView.viewName != "commits" {
|
||||||
bindings = append(bindings, &Binding{ViewName: listView.viewName, Contexts: []string{listView.context}, Key: gui.getKey("universal.startSearch"), Modifier: gocui.ModNone, Handler: gui.handleOpenSearch})
|
bindings = append(bindings, &Binding{
|
||||||
|
ViewName: listView.viewName,
|
||||||
|
Contexts: []string{listView.context},
|
||||||
|
Key: gui.getKey("universal.startSearch"),
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleOpenSearch,
|
||||||
|
Description: gui.Tr.SLocalize("startSearch"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, second
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil))
|
gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil))
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
@@ -220,6 +221,10 @@ func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.handleCycleLine(1)
|
return gui.handleCycleLine(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getSelectedCommitFileName() string {
|
||||||
|
return gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshMainView() error {
|
func (gui *Gui) refreshMainView() error {
|
||||||
state := gui.State.Panels.LineByLine
|
state := gui.State.Panels.LineByLine
|
||||||
|
|
||||||
@@ -227,7 +232,7 @@ func (gui *Gui) refreshMainView() error {
|
|||||||
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
||||||
// how to get around this
|
// how to get around this
|
||||||
if gui.State.MainContext == "patch-building" {
|
if gui.State.MainContext == "patch-building" {
|
||||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
filename := gui.getSelectedCommitFileName()
|
||||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||||
}
|
}
|
||||||
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
||||||
@@ -237,7 +242,8 @@ func (gui *Gui) refreshMainView() error {
|
|||||||
mainView.Wrap = false
|
mainView.Wrap = false
|
||||||
|
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
return gui.setViewContent(gui.g, gui.getMainView(), colorDiff)
|
gui.setViewContent(gui.g, gui.getMainView(), colorDiff)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ type menuItem struct {
|
|||||||
// list panel functions
|
// list panel functions
|
||||||
|
|
||||||
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
v.FocusPoint(0, gui.State.Panels.Menu.SelectedLine)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific functions
|
// specific functions
|
||||||
|
|||||||
+13
-7
@@ -151,13 +151,19 @@ func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644)
|
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return gui.refreshMergePanel()
|
return gui.refreshMergePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||||
gui.pushFileSnapshot(g)
|
if err := gui.pushFileSnapshot(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
pick := "bottom"
|
pick := "bottom"
|
||||||
if gui.State.Panels.Merging.ConflictTop {
|
if gui.State.Panels.Merging.ConflictTop {
|
||||||
pick = "top"
|
pick = "top"
|
||||||
@@ -178,7 +184,9 @@ func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
|||||||
|
|
||||||
func (gui *Gui) handlePickBothHunks(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]
|
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||||
gui.pushFileSnapshot(g)
|
if err := gui.pushFileSnapshot(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err := gui.resolveConflict(g, conflict, "both")
|
err := gui.resolveConflict(g, conflict, "both")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -215,9 +223,7 @@ func (gui *Gui) refreshMergePanel() error {
|
|||||||
|
|
||||||
mainView := gui.getMainView()
|
mainView := gui.getMainView()
|
||||||
mainView.Wrap = false
|
mainView.Wrap = false
|
||||||
if err := gui.setViewContent(gui.g, mainView, content); err != nil {
|
gui.setViewContent(gui.g, mainView, content)
|
||||||
return err
|
|
||||||
}
|
|
||||||
gui.Log.Warn("scrolling to conflict")
|
gui.Log.Warn("scrolling to conflict")
|
||||||
if err := gui.scrollToConflict(gui.g); err != nil {
|
if err := gui.scrollToConflict(gui.g); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -249,7 +255,7 @@ func (gui *Gui) renderMergeOptions() error {
|
|||||||
fmt.Sprintf("%s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock")): gui.Tr.SLocalize("navigateConflicts"),
|
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("universal.select"): gui.Tr.SLocalize("pickHunk"),
|
||||||
gui.getKeyDisplay("main.pickBothHunks"): gui.Tr.SLocalize("pickBothHunks"),
|
gui.getKeyDisplay("main.pickBothHunks"): gui.Tr.SLocalize("pickBothHunks"),
|
||||||
gui.getKeyDisplay("main.undo"): gui.Tr.SLocalize("undo"),
|
gui.getKeyDisplay("universal.undo"): gui.Tr.SLocalize("undo"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||||
@@ -17,7 +18,8 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
|||||||
// get diff from commit file that's currently selected
|
// get diff from commit file that's currently selected
|
||||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||||
if commitFile == nil {
|
if commitFile == nil {
|
||||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
diff, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true)
|
diff, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true)
|
||||||
@@ -42,38 +44,25 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleAddSelectionToPatch(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||||
state := gui.State.Panels.LineByLine
|
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
|
// add range of lines to those set for the file
|
||||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||||
if commitFile == nil {
|
if commitFile == nil {
|
||||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.GitCommand.PatchManager.AddFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
toggleFunc(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 {
|
if err := gui.refreshCommitFilesView(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -105,7 +94,8 @@ func (gui *Gui) refreshSecondaryPatchPanel() error {
|
|||||||
secondaryView.Wrap = false
|
secondaryView.Wrap = false
|
||||||
|
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false))
|
gui.setViewContent(gui.g, gui.getSecondaryView(), gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false))
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gui.State.SplitMainPanel = false
|
gui.State.SplitMainPanel = false
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error
|
|||||||
displayString: "pull patch out into index",
|
displayString: "pull patch out into index",
|
||||||
onPress: gui.handlePullPatchIntoWorkingTree,
|
onPress: gui.handlePullPatchIntoWorkingTree,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayString: "apply patch",
|
||||||
|
onPress: gui.handleApplyPatch,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayString: "reset patch",
|
displayString: "reset patch",
|
||||||
onPress: gui.handleResetPatch,
|
onPress: gui.handleResetPatch,
|
||||||
@@ -116,6 +120,17 @@ func (gui *Gui) handlePullPatchIntoWorkingTree() error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleApplyPatch() error {
|
||||||
|
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.GitCommand.PatchManager.ApplyPatches(false); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshSidePanels(gui.g)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleResetPatch() error {
|
func (gui *Gui) handleResetPatch() error {
|
||||||
gui.GitCommand.PatchManager.Reset()
|
gui.GitCommand.PatchManager.Reset()
|
||||||
return gui.refreshCommitFilesView()
|
return gui.refreshCommitFilesView()
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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 := b.Name
|
||||||
|
if b.DisplayName != "" {
|
||||||
|
displayName = b.DisplayName
|
||||||
|
}
|
||||||
|
coloredName := utils.ColoredString(displayName, 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)
|
||||||
|
coloredName = fmt.Sprintf("%s %s", coloredName, track)
|
||||||
|
}
|
||||||
|
|
||||||
|
recencyColor := color.FgCyan
|
||||||
|
if b.Recency == " *" {
|
||||||
|
recencyColor = color.FgGreen
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullDescription {
|
||||||
|
return []string{utils.ColoredString(b.Recency, recencyColor), coloredName, utils.ColoredString(b.UpstreamName, color.FgYellow)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{utils.ColoredString(b.Recency, recencyColor), coloredName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
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, cherryPickedCommitShaMap map[string]bool) [][]string {
|
||||||
|
lines := make([][]string, len(commits))
|
||||||
|
|
||||||
|
var displayFunc func(*commands.Commit, map[string]bool) []string
|
||||||
|
if fullDescription {
|
||||||
|
displayFunc = getFullDescriptionDisplayStringsForCommit
|
||||||
|
} else {
|
||||||
|
displayFunc = getDisplayStringsForCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range commits {
|
||||||
|
lines[i] = displayFunc(commits[i], cherryPickedCommitShaMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFullDescriptionDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map[string]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 "reflog":
|
||||||
|
shaColor = blue
|
||||||
|
case "selected":
|
||||||
|
shaColor = magenta
|
||||||
|
default:
|
||||||
|
shaColor = defaultColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if cherryPickedCommitShaMap[c.Sha] {
|
||||||
|
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.ShortSha()), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDisplayStringsForCommit(c *commands.Commit, cherryPickedCommitShaMap map[string]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 "reflog":
|
||||||
|
shaColor = blue
|
||||||
|
case "selected":
|
||||||
|
shaColor = magenta
|
||||||
|
default:
|
||||||
|
shaColor = defaultColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if cherryPickedCommitShaMap[c.Sha] {
|
||||||
|
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.ShortSha()), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package presentation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetReflogCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool) [][]string {
|
||||||
|
lines := make([][]string, len(commits))
|
||||||
|
|
||||||
|
var displayFunc func(*commands.Commit) []string
|
||||||
|
if fullDescription {
|
||||||
|
displayFunc = getFullDescriptionDisplayStringsForReflogCommit
|
||||||
|
} else {
|
||||||
|
displayFunc = getDisplayStringsForReflogCommit
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range commits {
|
||||||
|
lines[i] = displayFunc(commits[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFullDescriptionDisplayStringsForReflogCommit(c *commands.Commit) []string {
|
||||||
|
defaultColor := color.New(theme.DefaultTextColor)
|
||||||
|
|
||||||
|
return []string{utils.ColoredString(c.ShortSha(), color.FgBlue), utils.ColoredString(c.Date, color.FgMagenta), defaultColor.Sprint(c.Name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDisplayStringsForReflogCommit(c *commands.Commit) []string {
|
||||||
|
defaultColor := color.New(theme.DefaultTextColor)
|
||||||
|
|
||||||
|
return []string{utils.ColoredString(c.ShortSha(), color.FgBlue), defaultColor.Sprint(c.Name)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/creack/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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// +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)
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
|
|||||||
|
|
||||||
menuItems := make([]*menuItem, len(options))
|
menuItems := make([]*menuItem, len(options))
|
||||||
for i, option := range options {
|
for i, option := range options {
|
||||||
|
// note to self. Never, EVER, close over loop variables in a function
|
||||||
|
option := option
|
||||||
menuItems[i] = &menuItem{
|
menuItems[i] = &menuItem{
|
||||||
displayString: option,
|
displayString: option,
|
||||||
onPress: func() error {
|
onPress: func() error {
|
||||||
|
|||||||
+13
-10
@@ -3,6 +3,7 @@ package gui
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@@ -33,14 +34,12 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if commit == nil {
|
if commit == nil {
|
||||||
return gui.newStringTask("main", "No reflog history")
|
return gui.newStringTask("main", "No reflog history")
|
||||||
}
|
}
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.ShowCmdStr(commit.Sha),
|
gui.GitCommand.ShowCmdStr(commit.Sha),
|
||||||
)
|
)
|
||||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,12 +47,17 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshReflogCommits() error {
|
func (gui *Gui) refreshReflogCommits() error {
|
||||||
commits, err := gui.GitCommand.GetReflogCommits()
|
var lastReflogCommit *commands.Commit
|
||||||
|
if len(gui.State.ReflogCommits) > 0 {
|
||||||
|
lastReflogCommit = gui.State.ReflogCommits[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
commits, err := gui.GitCommand.GetNewReflogCommits(lastReflogCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.ReflogCommits = commits
|
gui.State.ReflogCommits = append(commits, gui.State.ReflogCommits...)
|
||||||
|
|
||||||
if gui.getCommitsView().Context == "reflog-commits" {
|
if gui.getCommitsView().Context == "reflog-commits" {
|
||||||
return gui.renderReflogCommitsWithSelection()
|
return gui.renderReflogCommitsWithSelection()
|
||||||
@@ -66,9 +70,8 @@ func (gui *Gui) renderReflogCommitsWithSelection() error {
|
|||||||
commitsView := gui.getCommitsView()
|
commitsView := gui.getCommitsView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits))
|
gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits))
|
||||||
if err := gui.renderListPanel(commitsView, gui.State.ReflogCommits); err != nil {
|
displayStrings := presentation.GetReflogCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||||
return err
|
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" {
|
if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" {
|
||||||
if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil {
|
if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -85,7 +88,7 @@ func (gui *Gui) handleCheckoutReflogCommit(g *gocui.Gui, v *gocui.View) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := gui.createConfirmationPanel(g, gui.getCommitsView(), true, gui.Tr.SLocalize("checkoutCommit"), gui.Tr.SLocalize("SureCheckoutThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
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)
|
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@@ -37,10 +38,7 @@ func (gui *Gui) handleRemoteBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.newStringTask("main", "No branches for this remote")
|
return gui.newStringTask("main", "No branches for this remote")
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
v.FocusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine)
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches), v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
branchName := fmt.Sprintf("%s/%s", remote.Name, remoteBranch.Name)
|
branchName := fmt.Sprintf("%s/%s", remote.Name, remoteBranch.Name)
|
||||||
|
|
||||||
@@ -62,9 +60,8 @@ func (gui *Gui) renderRemoteBranchesWithSelection() error {
|
|||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches))
|
gui.refreshSelectedLine(&gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches))
|
||||||
if err := gui.renderListPanel(branchesView, gui.State.RemoteBranches); err != nil {
|
displayStrings := presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches)
|
||||||
return err
|
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remote-branches" {
|
if gui.g.CurrentView() == branchesView && branchesView.Context == "remote-branches" {
|
||||||
if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil {
|
if err := gui.handleRemoteBranchSelect(gui.g, branchesView); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -79,7 +76,7 @@ func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if remoteBranch == nil {
|
if remoteBranch == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := gui.handleCheckoutRef(remoteBranch.RemoteName + "/" + remoteBranch.Name); err != nil {
|
if err := gui.handleCheckoutRef(remoteBranch.RemoteName+"/"+remoteBranch.Name, handleCheckoutRefOptions{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.switchBranchesPanelContext("local-branches")
|
return gui.switchBranchesPanelContext("local-branches")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,9 +39,7 @@ func (gui *Gui) handleRemoteSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if remote == nil {
|
if remote == nil {
|
||||||
return gui.newStringTask("main", "No remotes")
|
return gui.newStringTask("main", "No remotes")
|
||||||
}
|
}
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.Remotes.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.newStringTask("main", fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n")))
|
return gui.newStringTask("main", fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n")))
|
||||||
}
|
}
|
||||||
@@ -80,9 +79,10 @@ func (gui *Gui) renderRemotesWithSelection() error {
|
|||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes))
|
gui.refreshSelectedLine(&gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes))
|
||||||
if err := gui.renderListPanel(branchesView, gui.State.Remotes); err != nil {
|
|
||||||
return err
|
displayStrings := presentation.GetRemoteListDisplayStrings(gui.State.Remotes)
|
||||||
}
|
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||||
|
|
||||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "remotes" {
|
if gui.g.CurrentView() == branchesView && branchesView.Context == "remotes" {
|
||||||
if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil {
|
if err := gui.handleRemoteSelect(gui.g, branchesView); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+33
-20
@@ -4,13 +4,44 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (gui *Gui) resetToRef(ref string, strength string, options commands.RunCommandOptions) error {
|
||||||
|
if err := gui.GitCommand.ResetToCommit(ref, strength, options); 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
|
||||||
|
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||||
|
gui.State.Panels.Commits.LimitCommits = true
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) createResetMenu(ref string) error {
|
func (gui *Gui) createResetMenu(ref string) error {
|
||||||
strengths := []string{"soft", "mixed", "hard"}
|
strengths := []string{"soft", "mixed", "hard"}
|
||||||
menuItems := make([]*menuItem, len(strengths))
|
menuItems := make([]*menuItem, len(strengths))
|
||||||
for i, strength := range strengths {
|
for i, strength := range strengths {
|
||||||
innerStrength := strength
|
strength := strength
|
||||||
menuItems[i] = &menuItem{
|
menuItems[i] = &menuItem{
|
||||||
displayStrings: []string{
|
displayStrings: []string{
|
||||||
fmt.Sprintf("%s reset", strength),
|
fmt.Sprintf("%s reset", strength),
|
||||||
@@ -19,25 +50,7 @@ func (gui *Gui) createResetMenu(ref string) error {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
onPress: func() error {
|
onPress: func() error {
|
||||||
if err := gui.GitCommand.ResetToCommit(ref, innerStrength); err != nil {
|
return gui.resetToRef(ref, strength, commands.RunCommandOptions{})
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.switchCommitsPanelContext("branch-commits")
|
|
||||||
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.resetOrigin(gui.getCommitsView()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.handleCommitSelect(gui.g, gui.getCommitsView())
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
|
|||||||
gui.State.Searching.isSearching = true
|
gui.State.Searching.isSearching = true
|
||||||
gui.State.Searching.view = v
|
gui.State.Searching.view = v
|
||||||
gui.renderString(gui.g, "search", "")
|
gui.renderString(gui.g, "search", "")
|
||||||
gui.switchFocus(gui.g, v, gui.getSearchView())
|
if err := gui.switchFocus(gui.g, v, gui.getSearchView()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
||||||
gui.switchFocus(gui.g, nil, gui.State.Searching.view)
|
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 {
|
if err := gui.State.Searching.view.Search(gui.State.Searching.searchString); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,7 +90,5 @@ func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.onSearchEscape()
|
return gui.onSearchEscape()
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-21
@@ -99,26 +99,27 @@ func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleToggleStagedSelection(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.applySelectionWithPrompt(false)
|
state := gui.State.Panels.LineByLine
|
||||||
|
|
||||||
|
return gui.applySelection(state.SecondaryFocused)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.applySelectionWithPrompt(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) applySelectionWithPrompt(reverse bool) error {
|
|
||||||
state := gui.State.Panels.LineByLine
|
state := gui.State.Panels.LineByLine
|
||||||
|
|
||||||
if !reverse && state.SecondaryFocused {
|
if state.SecondaryFocused {
|
||||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
// for backwards compatibility
|
||||||
} else if reverse && !state.SecondaryFocused && !gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
return gui.applySelection(true)
|
||||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to unstage these lines? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", func(*gocui.Gui, *gocui.View) error {
|
|
||||||
return gui.applySelection(reverse)
|
|
||||||
}, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.applySelection(reverse)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) applySelection(reverse bool) error {
|
func (gui *Gui) applySelection(reverse bool) error {
|
||||||
@@ -158,11 +159,3 @@ func (gui *Gui) applySelection(reverse bool) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
+15
-20
@@ -1,11 +1,9 @@
|
|||||||
package gui
|
package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@@ -36,14 +34,12 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if stashEntry == nil {
|
if stashEntry == nil {
|
||||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries"))
|
return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries"))
|
||||||
}
|
}
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.Stash.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
|
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
|
||||||
)
|
)
|
||||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||||
gui.Log.Error(err)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,17 +52,12 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
|||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
||||||
|
|
||||||
isFocused := gui.g.CurrentView().Name() == "stash"
|
stashView := gui.getStashView()
|
||||||
list, err := utils.RenderList(gui.State.StashEntries, isFocused)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v := gui.getStashView()
|
displayStrings := presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries)
|
||||||
v.Clear()
|
gui.renderDisplayStrings(stashView, displayStrings)
|
||||||
fmt.Fprint(v, list)
|
|
||||||
|
|
||||||
if err := gui.resetOrigin(v); err != nil {
|
if err := gui.resetOrigin(stashView); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -104,9 +95,11 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
|
|||||||
return gui.createErrorPanel(g, errorMessage)
|
return gui.createErrorPanel(g, errorMessage)
|
||||||
}
|
}
|
||||||
if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil {
|
if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil {
|
||||||
gui.createErrorPanel(g, err.Error())
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := gui.refreshStashEntries(g); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
gui.refreshStashEntries(g)
|
|
||||||
return gui.refreshFiles()
|
return gui.refreshFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +109,11 @@ func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
|
|||||||
}
|
}
|
||||||
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 {
|
if err := stashFunc(gui.trimmedContent(v)); err != nil {
|
||||||
gui.createErrorPanel(g, err.Error())
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := gui.refreshStashEntries(g); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
}
|
}
|
||||||
gui.refreshStashEntries(g)
|
|
||||||
return gui.refreshFiles()
|
return gui.refreshFiles()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-11
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,11 +22,20 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
|||||||
// contents end up cleared
|
// contents end up cleared
|
||||||
g.Update(func(*gocui.Gui) error {
|
g.Update(func(*gocui.Gui) error {
|
||||||
v.Clear()
|
v.Clear()
|
||||||
|
// TODO: base this off of the current branch
|
||||||
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||||
if err := gui.updateWorkTreeState(); err != nil {
|
if err := gui.updateWorkTreeState(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
status := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
|
|
||||||
|
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)
|
||||||
branches := gui.State.Branches
|
branches := gui.State.Branches
|
||||||
|
|
||||||
if gui.State.WorkingTreeState != "normal" {
|
if gui.State.WorkingTreeState != "normal" {
|
||||||
@@ -35,7 +44,7 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
|||||||
|
|
||||||
if len(branches) > 0 {
|
if len(branches) > 0 {
|
||||||
branch := branches[0]
|
branch := branches[0]
|
||||||
name := utils.ColoredString(branch.Name, commands.GetBranchColor(branch.Name))
|
name := utils.ColoredString(branch.Name, presentation.GetBranchColor(branch.Name))
|
||||||
repoName := utils.GetCurrentRepoName()
|
repoName := utils.GetCurrentRepoName()
|
||||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||||
}
|
}
|
||||||
@@ -136,14 +145,6 @@ func lazygitTitle() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) updateWorkTreeState() error {
|
func (gui *Gui) updateWorkTreeState() error {
|
||||||
merging, err := gui.GitCommand.IsInMergeState()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if merging {
|
|
||||||
gui.State.WorkingTreeState = "merging"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rebaseMode, err := gui.GitCommand.RebaseMode()
|
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -152,6 +153,14 @@ func (gui *Gui) updateWorkTreeState() error {
|
|||||||
gui.State.WorkingTreeState = "rebasing"
|
gui.State.WorkingTreeState = "rebasing"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
merging, err := gui.GitCommand.IsInMergeState()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if merging {
|
||||||
|
gui.State.WorkingTreeState = "merging"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
gui.State.WorkingTreeState = "normal"
|
gui.State.WorkingTreeState = "normal"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-10
@@ -3,6 +3,7 @@ package gui
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
// list panel functions
|
||||||
@@ -33,9 +34,7 @@ func (gui *Gui) handleTagSelect(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if tag == nil {
|
if tag == nil {
|
||||||
return gui.newStringTask("main", "No tags")
|
return gui.newStringTask("main", "No tags")
|
||||||
}
|
}
|
||||||
if err := gui.focusPoint(0, gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags), v); err != nil {
|
v.FocusPoint(0, gui.State.Panels.Tags.SelectedLine)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := gui.OSCommand.ExecutableFromString(
|
cmd := gui.OSCommand.ExecutableFromString(
|
||||||
gui.GitCommand.GetBranchGraphCmdStr(tag.Name),
|
gui.GitCommand.GetBranchGraphCmdStr(tag.Name),
|
||||||
@@ -56,7 +55,7 @@ func (gui *Gui) refreshTags() error {
|
|||||||
gui.State.Tags = tags
|
gui.State.Tags = tags
|
||||||
|
|
||||||
if gui.getBranchesView().Context == "tags" {
|
if gui.getBranchesView().Context == "tags" {
|
||||||
gui.renderTagsWithSelection()
|
return gui.renderTagsWithSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -66,9 +65,8 @@ func (gui *Gui) renderTagsWithSelection() error {
|
|||||||
branchesView := gui.getBranchesView()
|
branchesView := gui.getBranchesView()
|
||||||
|
|
||||||
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
gui.refreshSelectedLine(&gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags))
|
||||||
if err := gui.renderListPanel(branchesView, gui.State.Tags); err != nil {
|
displayStrings := presentation.GetTagListDisplayStrings(gui.State.Tags)
|
||||||
return err
|
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||||
}
|
|
||||||
if gui.g.CurrentView() == branchesView && branchesView.Context == "tags" {
|
if gui.g.CurrentView() == branchesView && branchesView.Context == "tags" {
|
||||||
if err := gui.handleTagSelect(gui.g, branchesView); err != nil {
|
if err := gui.handleTagSelect(gui.g, branchesView); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -83,7 +81,7 @@ func (gui *Gui) handleCheckoutTag(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if tag == nil {
|
if tag == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := gui.handleCheckoutRef(tag.Name); err != nil {
|
if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.switchBranchesPanelContext("local-branches")
|
return gui.switchBranchesPanelContext("local-branches")
|
||||||
@@ -106,7 +104,13 @@ func (gui *Gui) handleDeleteTag(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
|
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
return gui.refreshTags()
|
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)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +141,13 @@ func (gui *Gui) handleCreateTag(g *gocui.Gui, v *gocui.View) error {
|
|||||||
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), ""); err != nil {
|
if err := gui.GitCommand.CreateLightweightTag(v.Buffer(), ""); err != nil {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
}
|
}
|
||||||
return gui.refreshTags()
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,17 @@ func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error {
|
|||||||
|
|
||||||
manager := gui.getManager(view)
|
manager := gui.getManager(view)
|
||||||
|
|
||||||
if err := manager.NewTask(manager.NewCmdTask(cmd, height+oy+10)); err != nil {
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +59,8 @@ func (gui *Gui) newStringTask(viewName string, str string) error {
|
|||||||
manager := gui.getManager(view)
|
manager := gui.getManager(view)
|
||||||
|
|
||||||
f := func(stop chan struct{}) error {
|
f := func(stop chan struct{}) error {
|
||||||
return gui.renderString(gui.g, viewName, str)
|
gui.renderString(gui.g, viewName, str)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.NewTask(f); err != nil {
|
if err := manager.NewTask(f); err != nil {
|
||||||
@@ -69,7 +80,9 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
|||||||
view.Clear()
|
view.Clear()
|
||||||
},
|
},
|
||||||
func() {
|
func() {
|
||||||
gui.g.Update(func(*gocui.Gui) error { return nil })
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
})
|
})
|
||||||
gui.viewBufferManagerMap[view.Name()] = manager
|
gui.viewBufferManagerMap[view.Name()] = manager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quick summary of how this all works:
|
||||||
|
// when you want to undo or redo, we start from the top of the reflog and work
|
||||||
|
// down until we've reached the last user-initiated reflog entry that hasn't already been undone
|
||||||
|
// we then do the reverse of what that reflog describes.
|
||||||
|
// When we do this, we create a new reflog entry, and tag it as either an undo or redo
|
||||||
|
// Then, next time we want to undo, we'll use those entries to know which user-initiated
|
||||||
|
// actions we can skip. E.g. if I do do three things, A, B, and C, and hit undo twice,
|
||||||
|
// the reflog will read UUCBA, and when I read the first two undos, I know to skip the following
|
||||||
|
// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way.
|
||||||
|
|
||||||
|
const (
|
||||||
|
CHECKOUT = iota
|
||||||
|
COMMIT
|
||||||
|
REBASE
|
||||||
|
CURRENT_REBASE
|
||||||
|
)
|
||||||
|
|
||||||
|
type reflogAction struct {
|
||||||
|
kind int // one of CHECKOUT, REBASE, and COMMIT
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we're going through the reflog and maintaining a counter that represents how many
|
||||||
|
// undos/redos/user actions we've seen. when we hit a user action we call the callback specifying
|
||||||
|
// what the counter is up to and the nature of the action.
|
||||||
|
// If we find ourselves mid-rebase, we just return because undo/redo mid rebase
|
||||||
|
// requires knowledge of previous TODO file states, which you can't just get from the reflog.
|
||||||
|
// Though we might support this later, hence the use of the CURRENT_REBASE action kind.
|
||||||
|
func (gui *Gui) parseReflogForActions(onUserAction func(counter int, action reflogAction) (bool, error)) error {
|
||||||
|
counter := 0
|
||||||
|
reflogCommits := gui.State.ReflogCommits
|
||||||
|
rebaseFinishCommitSha := ""
|
||||||
|
var action *reflogAction
|
||||||
|
for reflogCommitIdx, reflogCommit := range reflogCommits {
|
||||||
|
action = nil
|
||||||
|
|
||||||
|
prevCommitSha := ""
|
||||||
|
if len(reflogCommits)-1 >= reflogCommitIdx+1 {
|
||||||
|
prevCommitSha = reflogCommits[reflogCommitIdx+1].Sha
|
||||||
|
}
|
||||||
|
|
||||||
|
if rebaseFinishCommitSha == "" {
|
||||||
|
if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^\[lazygit undo\]`); ok {
|
||||||
|
counter++
|
||||||
|
} else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^\[lazygit redo\]`); ok {
|
||||||
|
counter--
|
||||||
|
} else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase -i \(abort\)|^rebase -i \(finish\)`); ok {
|
||||||
|
rebaseFinishCommitSha = reflogCommit.Sha
|
||||||
|
} else if ok, match := utils.FindStringSubmatch(reflogCommit.Name, `^checkout: moving from ([\S]+) to ([\S]+)`); ok {
|
||||||
|
action = &reflogAction{kind: CHECKOUT, from: match[1], to: match[2]}
|
||||||
|
} else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^commit|^reset: moving to|^pull`); ok {
|
||||||
|
action = &reflogAction{kind: COMMIT, from: prevCommitSha, to: reflogCommit.Sha}
|
||||||
|
} else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase -i \(start\)`); ok {
|
||||||
|
// if we're here then we must be currently inside an interactive rebase
|
||||||
|
action = &reflogAction{kind: CURRENT_REBASE, from: prevCommitSha}
|
||||||
|
}
|
||||||
|
} else if ok, _ := utils.FindStringSubmatch(reflogCommit.Name, `^rebase -i \(start\)`); ok {
|
||||||
|
action = &reflogAction{kind: REBASE, from: prevCommitSha, to: rebaseFinishCommitSha}
|
||||||
|
rebaseFinishCommitSha = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if action != nil {
|
||||||
|
if action.kind != CURRENT_REBASE && action.from == action.to {
|
||||||
|
// if we're going from one place to the same place we'll ignore the action.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok, err := onUserAction(counter, *action)
|
||||||
|
if ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
counter--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) reflogUndo(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
|
||||||
|
undoingStatus := gui.Tr.SLocalize("UndoingStatus")
|
||||||
|
|
||||||
|
if gui.State.WorkingTreeState == "rebasing" {
|
||||||
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("cantUndoWhileRebasing"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
|
||||||
|
if counter != 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action.kind {
|
||||||
|
case COMMIT, REBASE:
|
||||||
|
return true, gui.handleHardResetWithAutoStash(action.from, handleHardResetWithAutoStashOptions{
|
||||||
|
EnvVars: undoEnvVars,
|
||||||
|
WaitingStatus: undoingStatus,
|
||||||
|
})
|
||||||
|
case CHECKOUT:
|
||||||
|
return true, gui.handleCheckoutRef(action.from, handleCheckoutRefOptions{
|
||||||
|
EnvVars: undoEnvVars,
|
||||||
|
WaitingStatus: undoingStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.Log.Error("didn't match on the user action when trying to undo")
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) reflogRedo(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
|
||||||
|
redoingStatus := gui.Tr.SLocalize("RedoingStatus")
|
||||||
|
|
||||||
|
if gui.State.WorkingTreeState == "rebasing" {
|
||||||
|
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("cantRedoWhileRebasing"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.parseReflogForActions(func(counter int, action reflogAction) (bool, error) {
|
||||||
|
// if we're redoing and the counter is zero, we just return
|
||||||
|
if counter == 0 {
|
||||||
|
return true, nil
|
||||||
|
} else if counter > 1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action.kind {
|
||||||
|
case COMMIT, REBASE:
|
||||||
|
return true, gui.handleHardResetWithAutoStash(action.to, handleHardResetWithAutoStashOptions{
|
||||||
|
EnvVars: redoEnvVars,
|
||||||
|
WaitingStatus: redoingStatus,
|
||||||
|
})
|
||||||
|
case CHECKOUT:
|
||||||
|
return true, gui.handleCheckoutRef(action.to, handleCheckoutRefOptions{
|
||||||
|
EnvVars: redoEnvVars,
|
||||||
|
WaitingStatus: redoingStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.Log.Error("didn't match on the user action when trying to redo")
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type handleHardResetWithAutoStashOptions struct {
|
||||||
|
WaitingStatus string
|
||||||
|
EnvVars []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// only to be used in the undo flow for now
|
||||||
|
func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
|
||||||
|
// if we have any modified tracked files we need to ask the user if they want us to stash for them
|
||||||
|
dirtyWorkingTree := false
|
||||||
|
for _, file := range gui.State.Files {
|
||||||
|
if file.Tracked {
|
||||||
|
dirtyWorkingTree = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset := func() error {
|
||||||
|
if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirtyWorkingTree {
|
||||||
|
// 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 {
|
||||||
|
return gui.WithWaitingStatus(options.WaitingStatus, func() error {
|
||||||
|
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + commitSha); err != nil {
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
if err := reset(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
|
||||||
|
if err := gui.refreshSidePanels(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.createErrorPanel(g, err.Error())
|
||||||
|
}
|
||||||
|
return gui.refreshSidePanels(g)
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.WithWaitingStatus(options.WaitingStatus, func() error {
|
||||||
|
if err := reset(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gui.refreshSidePanels(gui.g)
|
||||||
|
})
|
||||||
|
}
|
||||||
+1
-3
@@ -47,9 +47,7 @@ func (gui *Gui) startUpdating(newVersion string) {
|
|||||||
func (gui *Gui) onUpdateFinish(err error) error {
|
func (gui *Gui) onUpdateFinish(err error) error {
|
||||||
gui.State.Updating = false
|
gui.State.Updating = false
|
||||||
gui.statusManager.removeStatus("updating")
|
gui.statusManager.removeStatus("updating")
|
||||||
if err := gui.renderString(gui.g, "appStatus", ""); err != nil {
|
gui.renderString(gui.g, "appStatus", "")
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gui.createErrorPanel(gui.g, "Update failed: "+err.Error())
|
return gui.createErrorPanel(gui.g, "Update failed: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-40
@@ -218,25 +218,18 @@ func (gui *Gui) resetOrigin(v *gocui.View) error {
|
|||||||
return v.SetOrigin(0, 0)
|
return v.SetOrigin(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the cursor down past the last item, move it to the last line
|
|
||||||
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
|
||||||
v.FocusPoint(cx, cy)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) cleanString(s string) string {
|
func (gui *Gui) cleanString(s string) string {
|
||||||
output := string(bom.Clean([]byte(s)))
|
output := string(bom.Clean([]byte(s)))
|
||||||
return utils.NormalizeLinefeeds(output)
|
return utils.NormalizeLinefeeds(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
|
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) {
|
||||||
v.Clear()
|
v.Clear()
|
||||||
fmt.Fprint(v, gui.cleanString(s))
|
fmt.Fprint(v, gui.cleanString(s))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderString resets the origin of a view and sets its content
|
// renderString resets the origin of a view and sets its content
|
||||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) {
|
||||||
g.Update(func(*gocui.Gui) error {
|
g.Update(func(*gocui.Gui) error {
|
||||||
v, err := g.View(viewName)
|
v, err := g.View(viewName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -248,9 +241,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
|||||||
if err := v.SetCursor(0, 0); err != nil {
|
if err := v.SetCursor(0, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return gui.setViewContent(gui.g, v, s)
|
gui.setViewContent(gui.g, v, s)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||||
@@ -263,7 +256,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
|
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
|
||||||
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor properly
|
// TODO: refactor properly
|
||||||
@@ -349,22 +343,6 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 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, change int) {
|
func (gui *Gui) changeSelectedLine(line *int, total int, change int) {
|
||||||
// TODO: find out why we're doing this
|
// TODO: find out why we're doing this
|
||||||
if *line == -1 {
|
if *line == -1 {
|
||||||
@@ -387,18 +365,13 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
|
func (gui *Gui) renderDisplayStrings(v *gocui.View, displayStrings [][]string) {
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
isFocused := gui.g.CurrentView().Name() == v.Name()
|
list := utils.RenderDisplayStrings(displayStrings)
|
||||||
list, err := utils.RenderList(items, isFocused)
|
|
||||||
if err != nil {
|
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
|
||||||
}
|
|
||||||
v.Clear()
|
v.Clear()
|
||||||
fmt.Fprint(v, list)
|
fmt.Fprint(v, list)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderPanelOptions() error {
|
func (gui *Gui) renderPanelOptions() error {
|
||||||
@@ -414,11 +387,6 @@ func (gui *Gui) renderPanelOptions() error {
|
|||||||
return gui.renderGlobalOptions()
|
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 {
|
func (gui *Gui) isPopupPanel(viewName string) bool {
|
||||||
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
|
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-3
@@ -360,6 +360,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "undo",
|
ID: "undo",
|
||||||
Other: "undo",
|
Other: "undo",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "undoReflog",
|
||||||
|
Other: "undo (via reflog) (experimental)",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "redoReflog",
|
||||||
|
Other: "redo (via reflog) (experimental)",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "pop",
|
ID: "pop",
|
||||||
Other: "pop",
|
Other: "pop",
|
||||||
@@ -501,16 +507,19 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
Other: `select hunk`,
|
Other: `select hunk`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "StageSelection",
|
ID: "StageSelection",
|
||||||
Other: `stage selection`,
|
Other: `toggle line staged / unstaged`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "ResetSelection",
|
ID: "ResetSelection",
|
||||||
Other: `reset selection`,
|
Other: `delete change (git reset)`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "ToggleDragSelect",
|
ID: "ToggleDragSelect",
|
||||||
Other: `toggle drag select`,
|
Other: `toggle drag select`,
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "ToggleSelectHunk",
|
ID: "ToggleSelectHunk",
|
||||||
Other: `toggle select hunk`,
|
Other: `toggle select hunk`,
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "ToggleSelectionForPatch",
|
||||||
|
Other: `add/remove line(s) to patch`,
|
||||||
},
|
},
|
||||||
&i18n.Message{
|
&i18n.Message{
|
||||||
ID: "TogglePanel",
|
ID: "TogglePanel",
|
||||||
@@ -564,6 +573,54 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "RebaseOptionsTitle",
|
ID: "RebaseOptionsTitle",
|
||||||
Other: "Rebase Options",
|
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{
|
}, &i18n.Message{
|
||||||
ID: "ConflictsResolved",
|
ID: "ConflictsResolved",
|
||||||
Other: "all merge conflicts resolved. Continue?",
|
Other: "all merge conflicts resolved. Continue?",
|
||||||
@@ -651,6 +708,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "ScrollUp",
|
ID: "ScrollUp",
|
||||||
Other: "scroll up",
|
Other: "scroll up",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "scrollUpMainPanel",
|
||||||
|
Other: "scroll up main panel",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "scrollDownMainPanel",
|
||||||
|
Other: "scroll down main panel",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "AmendCommitTitle",
|
ID: "AmendCommitTitle",
|
||||||
Other: "Amend Commit",
|
Other: "Amend Commit",
|
||||||
@@ -684,6 +747,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CherryPickingStatus",
|
ID: "CherryPickingStatus",
|
||||||
Other: "cherry-picking",
|
Other: "cherry-picking",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "UndoingStatus",
|
||||||
|
Other: "undoing",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "RedoingStatus",
|
||||||
|
Other: "redoing",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "CheckingOutStatus",
|
||||||
|
Other: "checking out",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitFiles",
|
ID: "CommitFiles",
|
||||||
Other: "Commit files",
|
Other: "Commit files",
|
||||||
@@ -692,7 +764,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
Other: "view commit's files",
|
Other: "view commit's files",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "CommitFilesTitle",
|
ID: "CommitFilesTitle",
|
||||||
Other: "Commit files",
|
Other: "Commit Files",
|
||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "goBack",
|
ID: "goBack",
|
||||||
Other: "go back",
|
Other: "go back",
|
||||||
@@ -945,6 +1017,51 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
|||||||
}, &i18n.Message{
|
}, &i18n.Message{
|
||||||
ID: "viewResetToUpstreamOptions",
|
ID: "viewResetToUpstreamOptions",
|
||||||
Other: "view upstream reset options",
|
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",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "resetCherryPick",
|
||||||
|
Other: "reset cherry-picked (copied) commits selection",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "nextTab",
|
||||||
|
Other: "next tab",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "prevTab",
|
||||||
|
Other: "previous tab",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "cantUndoWhileRebasing",
|
||||||
|
Other: "Can't undo while rebasing",
|
||||||
|
}, &i18n.Message{
|
||||||
|
ID: "cantRedoWhileRebasing",
|
||||||
|
Other: "Can't redo while rebasing",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-30
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,7 +23,6 @@ type Task struct {
|
|||||||
|
|
||||||
type ViewBufferManager struct {
|
type ViewBufferManager struct {
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
waitingTask *Task
|
|
||||||
currentTask *Task
|
currentTask *Task
|
||||||
waitingMutex sync.Mutex
|
waitingMutex sync.Mutex
|
||||||
taskIDMutex sync.Mutex
|
taskIDMutex sync.Mutex
|
||||||
@@ -45,27 +45,20 @@ func (m *ViewBufferManager) ReadLines(n int) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan struct{}) error {
|
func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, linesToRead int, onDone func()) func(chan struct{}) error {
|
||||||
return func(stop chan struct{}) error {
|
return func(stop chan struct{}) error {
|
||||||
r, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd.Stderr = cmd.Stdout
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-stop
|
<-stop
|
||||||
if cmd.ProcessState == nil {
|
if err := commands.Kill(cmd); err != nil {
|
||||||
if err := kill(cmd); err != nil {
|
m.Log.Warn(err)
|
||||||
m.Log.Warn(err)
|
}
|
||||||
}
|
if onDone != nil {
|
||||||
|
onDone()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
loadingMutex := sync.Mutex{}
|
||||||
|
|
||||||
// not sure if it's the right move to redefine this or not
|
// not sure if it's the right move to redefine this or not
|
||||||
m.readLines = make(chan int, 1024)
|
m.readLines = make(chan int, 1024)
|
||||||
|
|
||||||
@@ -82,11 +75,13 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
loadingMutex.Lock()
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.beforeStart()
|
m.beforeStart()
|
||||||
m.writer.Write([]byte("loading..."))
|
_, _ = m.writer.Write([]byte("loading..."))
|
||||||
m.refreshView()
|
m.refreshView()
|
||||||
}
|
}
|
||||||
|
loadingMutex.Unlock()
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -98,13 +93,16 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
|||||||
case linesToRead := <-m.readLines:
|
case linesToRead := <-m.readLines:
|
||||||
for i := 0; i < linesToRead; i++ {
|
for i := 0; i < linesToRead; i++ {
|
||||||
ok := scanner.Scan()
|
ok := scanner.Scan()
|
||||||
|
loadingMutex.Lock()
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.beforeStart()
|
m.beforeStart()
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
loadingMutex.Unlock()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-stop:
|
||||||
|
m.refreshView()
|
||||||
break outer
|
break outer
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -112,10 +110,11 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
|||||||
m.refreshView()
|
m.refreshView()
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
_, _ = m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||||
}
|
}
|
||||||
m.refreshView()
|
m.refreshView()
|
||||||
case <-stop:
|
case <-stop:
|
||||||
|
m.refreshView()
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,6 +123,12 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
|||||||
m.Log.Warn(err)
|
m.Log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.refreshView()
|
||||||
|
|
||||||
|
if onDone != nil {
|
||||||
|
onDone()
|
||||||
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -141,7 +146,7 @@ func (t *ViewBufferManager) Close() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c := make(chan struct{}, 1)
|
c := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
t.currentTask.Stop()
|
t.currentTask.Stop()
|
||||||
@@ -170,7 +175,10 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
|
|||||||
|
|
||||||
m.waitingMutex.Lock()
|
m.waitingMutex.Lock()
|
||||||
defer m.waitingMutex.Unlock()
|
defer m.waitingMutex.Unlock()
|
||||||
|
|
||||||
|
m.Log.Infof("done waiting")
|
||||||
if taskID < m.newTaskId {
|
if taskID < m.newTaskId {
|
||||||
|
m.Log.Infof("returning cos the task is obsolete")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,15 +222,4 @@ func (t *Task) Stop() {
|
|||||||
<-t.notifyStopped
|
<-t.notifyStopped
|
||||||
t.Log.Info("received notifystopped message")
|
t.Log.Info("received notifystopped message")
|
||||||
t.stopped = true
|
t.stopped = true
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// kill kills a process
|
|
||||||
func kill(cmd *exec.Cmd) error {
|
|
||||||
if cmd.Process == nil {
|
|
||||||
// somebody got to it before we were able to, poor bastard
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.Process.Kill()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-51
@@ -6,13 +6,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,46 +111,6 @@ func Min(x, y int) int {
|
|||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
type Displayable interface {
|
|
||||||
GetDisplayStrings(bool) []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
return RenderDisplayStrings(stringArrays), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderDisplayStrings(displayStringsArr [][]string) string {
|
func RenderDisplayStrings(displayStringsArr [][]string) string {
|
||||||
padWidths := getPadWidths(displayStringsArr)
|
padWidths := getPadWidths(displayStringsArr)
|
||||||
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
|
paddedDisplayStrings := getPaddedDisplayStrings(displayStringsArr, padWidths)
|
||||||
@@ -220,14 +177,6 @@ func displayArraysAligned(stringArrays [][]string) bool {
|
|||||||
return true
|
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
|
// IncludesString if the list contains the string
|
||||||
func IncludesString(list []string, a string) bool {
|
func IncludesString(list []string, a string) bool {
|
||||||
for _, b := range list {
|
for _, b := range list {
|
||||||
@@ -322,3 +271,54 @@ func ModuloWithWrap(n, max int) int {
|
|||||||
return n
|
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] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindStringSubmatch(str string, regexpStr string) (bool, []string) {
|
||||||
|
re := regexp.MustCompile(regexpStr)
|
||||||
|
match := re.FindStringSubmatch(str)
|
||||||
|
return len(match) > 0, match
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,166 +196,6 @@ 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,
|
|
||||||
"a \nb c",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]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.
|
// TestGetPaddedDisplayStrings is a function.
|
||||||
func TestGetPaddedDisplayStrings(t *testing.T) {
|
func TestGetPaddedDisplayStrings(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/app"
|
"github.com/jesseduffield/lazygit/pkg/app"
|
||||||
@@ -60,33 +61,85 @@ func formatTitle(title string) string {
|
|||||||
|
|
||||||
func formatBinding(binding *gui.Binding) string {
|
func formatBinding(binding *gui.Binding) string {
|
||||||
if binding.Alternative != "" {
|
if binding.Alternative != "" {
|
||||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
|
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
|
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBindingSections(mApp *app.App) []*bindingSection {
|
func getBindingSections(mApp *app.App) []*bindingSection {
|
||||||
bindingSections := []*bindingSection{}
|
bindingSections := []*bindingSection{}
|
||||||
|
|
||||||
// TODO: add context-based keybindings
|
bindings := mApp.Gui.GetInitialKeybindings()
|
||||||
for _, binding := range mApp.Gui.GetInitialKeybindings() {
|
|
||||||
if binding.Description == "" {
|
type contextAndViewType struct {
|
||||||
continue
|
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...)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewName := binding.ViewName
|
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
|
||||||
if viewName == "" {
|
if viewName == "" {
|
||||||
viewName = "global"
|
viewName = "global"
|
||||||
}
|
}
|
||||||
title := localisedTitle(mApp, viewName)
|
translatedView := localisedTitle(mApp, viewName)
|
||||||
|
var title string
|
||||||
bindingSections = addBinding(title, bindingSections, binding)
|
if contextAndView.context == "" {
|
||||||
}
|
addendum := " " + mApp.Tr.SLocalize("Panel")
|
||||||
|
if viewName == "global" {
|
||||||
for contextName, contextBindings := range mApp.Gui.GetContextMap() {
|
addendum = ""
|
||||||
translatedView := localisedTitle(mApp, contextBindings[0].ViewName)
|
}
|
||||||
translatedContextName := localisedTitle(mApp, contextName)
|
title = fmt.Sprintf("%s%s", translatedView, addendum)
|
||||||
title := fmt.Sprintf("%s (%s)", translatedView, translatedContextName)
|
} else {
|
||||||
|
translatedContextName := localisedTitle(mApp, contextAndView.context)
|
||||||
|
title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.SLocalize("Panel"), translatedContextName)
|
||||||
|
}
|
||||||
|
|
||||||
for _, binding := range contextBindings {
|
for _, binding := range contextBindings {
|
||||||
bindingSections = addBinding(title, bindingSections, binding)
|
bindingSections = addBinding(title, bindingSections, binding)
|
||||||
@@ -117,7 +170,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
|
func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
|
||||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu"))
|
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("Keybindings"))
|
||||||
|
|
||||||
for _, section := range bindingSections {
|
for _, section := range bindingSections {
|
||||||
content += formatTitle(section.title)
|
content += formatTitle(section.title)
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
FROM golang:1.13
|
||||||
|
|
||||||
|
# Clone and complie a riscv compatible version of the go compiler.
|
||||||
|
RUN git clone https://review.gerrithub.io/riscv/riscv-go /riscv-go
|
||||||
|
# riscvdev branch HEAD as of 2019-06-29.
|
||||||
|
RUN cd /riscv-go && git checkout 04885fddd096d09d4450726064d06dd107e374bf
|
||||||
|
ENV PATH=/riscv-go/misc/riscv:/riscv-go/bin:$PATH
|
||||||
|
RUN cd /riscv-go/src && GOROOT_BOOTSTRAP=$(go env GOROOT) ./make.bash
|
||||||
|
ENV GOROOT=/riscv-go
|
||||||
|
|
||||||
|
# Make sure we compile.
|
||||||
|
WORKDIR pty
|
||||||
|
ADD . .
|
||||||
|
RUN GOOS=linux GOARCH=riscv go build
|
||||||
Generated
Vendored
Generated
Vendored
+3
-3
@@ -4,7 +4,7 @@ Pty is a Go package for using unix pseudo-terminals.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
go get github.com/kr/pty
|
go get github.com/creack/pty
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Pty is a Go package for using unix pseudo-terminals.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kr/pty"
|
"github.com/creack/pty"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -50,7 +50,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/kr/pty"
|
"github.com/creack/pty"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
module github.com/creack/pty
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
// +build !windows,!solaris
|
||||||
|
|
||||||
package pty
|
package pty
|
||||||
|
|
||||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// see /usr/include/sys/stropts.h
|
||||||
|
I_PUSH = uintptr((int32('S')<<8 | 002))
|
||||||
|
I_STR = uintptr((int32('S')<<8 | 010))
|
||||||
|
I_FIND = uintptr((int32('S')<<8 | 013))
|
||||||
|
// see /usr/include/sys/ptms.h
|
||||||
|
ISPTM = (int32('P') << 8) | 1
|
||||||
|
UNLKPT = (int32('P') << 8) | 2
|
||||||
|
PTSSTTY = (int32('P') << 8) | 3
|
||||||
|
ZONEPT = (int32('P') << 8) | 4
|
||||||
|
OWNERPT = (int32('P') << 8) | 5
|
||||||
|
)
|
||||||
|
|
||||||
|
type strioctl struct {
|
||||||
|
ic_cmd int32
|
||||||
|
ic_timout int32
|
||||||
|
ic_len int32
|
||||||
|
ic_dp unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctl(fd, cmd, ptr uintptr) error {
|
||||||
|
return unix.IoctlSetInt(int(fd), uint(cmd), int(ptr))
|
||||||
|
}
|
||||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+1
-1
@@ -46,6 +46,6 @@ func ptsname(f *os.File) (string, error) {
|
|||||||
|
|
||||||
func unlockpt(f *os.File) error {
|
func unlockpt(f *os.File) error {
|
||||||
var u _C_int
|
var u _C_int
|
||||||
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
|
// use TIOCSPTLCK with a pointer to zero to clear the lock
|
||||||
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||||
}
|
}
|
||||||
Generated
Vendored
+1
-1
@@ -11,7 +11,7 @@ func open() (pty, tty *os.File, err error) {
|
|||||||
* from ptm(4):
|
* from ptm(4):
|
||||||
* The PTMGET command allocates a free pseudo terminal, changes its
|
* The PTMGET command allocates a free pseudo terminal, changes its
|
||||||
* ownership to the caller, revokes the access privileges for all previous
|
* ownership to the caller, revokes the access privileges for all previous
|
||||||
* users, opens the file descriptors for the master and slave devices and
|
* users, opens the file descriptors for the pty and tty devices and
|
||||||
* returns them to the caller in struct ptmget.
|
* returns them to the caller in struct ptmget.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
/* based on:
|
||||||
|
http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/pt.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const NODEV = ^uint64(0)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|unix.O_NOCTTY, 0)
|
||||||
|
//masterfd, err := syscall.Open("/dev/ptmx", syscall.O_RDWR|syscall.O_CLOEXEC|unix.O_NOCTTY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
p := os.NewFile(uintptr(masterfd), "/dev/ptmx")
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = grantpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unlockpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
slavefd, err := syscall.Open(sname, os.O_RDWR|unix.O_NOCTTY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
t := os.NewFile(uintptr(slavefd), sname)
|
||||||
|
|
||||||
|
// pushing terminal driver STREAMS modules as per pts(7)
|
||||||
|
for _, mod := range([]string{"ptem", "ldterm", "ttcompat"}) {
|
||||||
|
err = streams_push(t, mod)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func minor(x uint64) uint64 {
|
||||||
|
return x & 0377
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsdev(fd uintptr) uint64 {
|
||||||
|
istr := strioctl{ISPTM, 0, 0, nil}
|
||||||
|
err := ioctl(fd, I_STR, uintptr(unsafe.Pointer(&istr)))
|
||||||
|
if err != nil {
|
||||||
|
return NODEV
|
||||||
|
}
|
||||||
|
var status unix.Stat_t
|
||||||
|
err = unix.Fstat(int(fd), &status)
|
||||||
|
if err != nil {
|
||||||
|
return NODEV
|
||||||
|
}
|
||||||
|
return uint64(minor(status.Rdev))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
dev := ptsdev(f.Fd())
|
||||||
|
if dev == NODEV {
|
||||||
|
return "", errors.New("not a master pty")
|
||||||
|
}
|
||||||
|
fn := "/dev/pts/" + strconv.FormatInt(int64(dev), 10)
|
||||||
|
// access(2) creates the slave device (if the pty exists)
|
||||||
|
// F_OK == 0 (unistd.h)
|
||||||
|
err := unix.Access(fn, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pt_own struct {
|
||||||
|
pto_ruid int32
|
||||||
|
pto_rgid int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantpt(f *os.File) error {
|
||||||
|
if ptsdev(f.Fd()) == NODEV {
|
||||||
|
return errors.New("not a master pty")
|
||||||
|
}
|
||||||
|
var pto pt_own
|
||||||
|
pto.pto_ruid = int32(os.Getuid())
|
||||||
|
// XXX should first attempt to get gid of DEFAULT_TTY_GROUP="tty"
|
||||||
|
pto.pto_rgid = int32(os.Getgid())
|
||||||
|
var istr strioctl
|
||||||
|
istr.ic_cmd = OWNERPT
|
||||||
|
istr.ic_timout = 0
|
||||||
|
istr.ic_len = int32(unsafe.Sizeof(istr))
|
||||||
|
istr.ic_dp = unsafe.Pointer(&pto)
|
||||||
|
err := ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("access denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
istr := strioctl{UNLKPT, 0, 0, nil}
|
||||||
|
return ioctl(f.Fd(), I_STR, uintptr(unsafe.Pointer(&istr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// push STREAMS modules if not already done so
|
||||||
|
func streams_push(f *os.File, mod string) error {
|
||||||
|
var err error
|
||||||
|
buf := []byte(mod)
|
||||||
|
// XXX I_FIND is not returning an error when the module
|
||||||
|
// is already pushed even though truss reports a return
|
||||||
|
// value of 1. A bug in the Go Solaris syscall interface?
|
||||||
|
// XXX without this we are at risk of the issue
|
||||||
|
// https://www.illumos.org/issues/9042
|
||||||
|
// but since we are not using libc or XPG4.2, we should not be
|
||||||
|
// double-pushing modules
|
||||||
|
|
||||||
|
err = ioctl(f.Fd(), I_FIND, uintptr(unsafe.Pointer(&buf[0])))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = ioctl(f.Fd(), I_PUSH, uintptr(unsafe.Pointer(&buf[0])))
|
||||||
|
return err
|
||||||
|
}
|
||||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd
|
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris
|
||||||
|
|
||||||
package pty
|
package pty
|
||||||
|
|
||||||
+4
-1
@@ -39,12 +39,15 @@ func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
|
|||||||
if c.Stderr == nil {
|
if c.Stderr == nil {
|
||||||
c.Stderr = tty
|
c.Stderr = tty
|
||||||
}
|
}
|
||||||
c.Stdin = tty
|
if c.Stdin == nil {
|
||||||
|
c.Stdin = tty
|
||||||
|
}
|
||||||
if c.SysProcAttr == nil {
|
if c.SysProcAttr == nil {
|
||||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
}
|
}
|
||||||
c.SysProcAttr.Setctty = true
|
c.SysProcAttr.Setctty = true
|
||||||
c.SysProcAttr.Setsid = true
|
c.SysProcAttr.Setsid = true
|
||||||
|
c.SysProcAttr.Ctty = int(tty.Fd())
|
||||||
err = c.Start()
|
err = c.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pty.Close()
|
pty.Close()
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Test script checking that all expected os/arch compile properly.
|
||||||
|
# Does not actually test the logic, just the compilation so we make sure we don't break code depending on the lib.
|
||||||
|
|
||||||
|
echo2() {
|
||||||
|
echo $@ >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
trap end 0
|
||||||
|
end() {
|
||||||
|
[ "$?" = 0 ] && echo2 "Pass." || (echo2 "Fail."; exit 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cross() {
|
||||||
|
os=$1
|
||||||
|
shift
|
||||||
|
echo2 "Build for $os."
|
||||||
|
for arch in $@; do
|
||||||
|
echo2 " - $os/$arch"
|
||||||
|
GOOS=$os GOARCH=$arch go build
|
||||||
|
done
|
||||||
|
echo2
|
||||||
|
}
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cross linux amd64 386 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le
|
||||||
|
cross darwin amd64 386 arm arm64
|
||||||
|
cross freebsd amd64 386 arm
|
||||||
|
cross netbsd amd64 386 arm
|
||||||
|
cross openbsd amd64 386
|
||||||
|
cross dragonfly amd64
|
||||||
|
cross solaris amd64
|
||||||
|
|
||||||
|
# Not expected to work but should still compile.
|
||||||
|
cross windows amd64 386 arm
|
||||||
|
|
||||||
|
# TODO: Fix compilation error on openbsd/arm.
|
||||||
|
# TODO: Merge the solaris PR.
|
||||||
|
|
||||||
|
# Some os/arch require a different compiler. Run in docker.
|
||||||
|
if ! hash docker; then
|
||||||
|
# If docker is not present, stop here.
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo2 "Build for linux."
|
||||||
|
echo2 " - linux/riscv"
|
||||||
|
docker build -t test -f Dockerfile.riscv .
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user