mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 10:16:22 -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 |
@@ -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 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/
|
||||
COPY ./ .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build
|
||||
|
||||
FROM alpine:3.10
|
||||
FROM alpine:3.11
|
||||
RUN apk add -U git xdg-utils
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY --from=0 /go/src/github.com/jesseduffield/lazygit /go/src/github.com/jesseduffield/lazygit
|
||||
|
||||
@@ -1,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.
|
||||
|
||||

|
||||

|
||||
|
||||
- [Installation](https://github.com/jesseduffield/lazygit#installation)
|
||||
- [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)
|
||||
## Table of contents
|
||||
|
||||
[<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)
|
||||
|
||||
[<img src="https://i.imgur.com/sVEktDn.png">](https://youtu.be/CPLdltN7wgE)
|
||||
|
||||
## Installation
|
||||
|
||||
### Binary Releases
|
||||
|
||||
For Windows, Mac OS or Linux, you can download a binary release [here](/releases).
|
||||
|
||||
### Homebrew
|
||||
|
||||
Normally the lazygit formula can be found in the Homebrew core but we suggest you tap our formula to get the frequently updated one. It works with Linux, too.
|
||||
|
||||
Tap:
|
||||
|
||||
```
|
||||
brew install jesseduffield/lazygit/lazygit
|
||||
```
|
||||
@@ -38,8 +62,10 @@ brew install lazygit
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
|
||||
Latest version built from github releases.
|
||||
Tap:
|
||||
|
||||
```
|
||||
sudo port install lazygit
|
||||
```
|
||||
@@ -63,6 +89,7 @@ They follow upstream latest releases
|
||||
```sh
|
||||
sudo xbps-install -S lazygit
|
||||
```
|
||||
|
||||
### Scoop (Windows)
|
||||
|
||||
You can install `lazygit` using [scoop](https://scoop.sh/). It's in the `extras` bucket:
|
||||
@@ -82,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
|
||||
and the git version which builds from the most recent commit.
|
||||
|
||||
- Stable: https://aur.archlinux.org/packages/lazygit/
|
||||
- Development: https://aur.archlinux.org/packages/lazygit-git/
|
||||
- Stable: <https://aur.archlinux.org/packages/lazygit/>
|
||||
- Development: <https://aur.archlinux.org/packages/lazygit-git/>
|
||||
|
||||
Instruction of how to install AUR content can be found here:
|
||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
<https://wiki.archlinux.org/index.php/Arch_User_Repository>
|
||||
|
||||
### Fedora and CentOS 7
|
||||
|
||||
@@ -99,16 +126,12 @@ sudo dnf install lazygit
|
||||
|
||||
### Conda
|
||||
|
||||
Released versions are available for different platforms, see https://anaconda.org/conda-forge/lazygit
|
||||
Released versions are available for different platforms, see <https://anaconda.org/conda-forge/lazygit>
|
||||
|
||||
```sh
|
||||
conda install -c conda-forge lazygit
|
||||
```
|
||||
|
||||
### Binary Release (Windows/Linux/OSX)
|
||||
|
||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases).
|
||||
|
||||
### Go
|
||||
|
||||
```sh
|
||||
@@ -121,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,
|
||||
not apps like Lazygit).
|
||||
|
||||
|
||||
## 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
|
||||
whichever rc file you're using).
|
||||
|
||||
- Basic video tutorial [here](https://youtu.be/VDXvbHZYeKY).
|
||||
- Rebase Magic tutorial [here](https://youtu.be/4XaToVut_hs)
|
||||
- List of keybindings
|
||||
[here](/docs/keybindings).
|
||||
|
||||
## Changing Directory On Exit
|
||||
### Keybindings
|
||||
|
||||
You can check out the list of keybindings [here](/docs/keybindings).
|
||||
|
||||
### Changing Directory On Exit
|
||||
|
||||
If you change repos in lazygit and want your shell to change directory into that repo on exiting lazygit, add this to your `~/.zshrc` (or other rc file):
|
||||
|
||||
```
|
||||
lg()
|
||||
{
|
||||
@@ -148,8 +178,28 @@ lg()
|
||||
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`.
|
||||
|
||||
### 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
|
||||
|
||||
- Adding files easily
|
||||
@@ -165,14 +215,14 @@ Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch
|
||||
|
||||
### Interactive Rebasing
|
||||
|
||||

|
||||

|
||||
|
||||
## Contributing
|
||||
|
||||
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
|
||||
|
||||
[](https://join.slack.com/t/lazygit/shared_invite/enQtNDE3MjIwNTYyMDA0LTM3Yjk3NzdiYzhhNTA1YjM4Y2M4MWNmNDBkOTI0YTE4YjQ1ZmI2YWRhZTgwNjg2YzhhYjg3NDBlMmQyMTI5N2M)
|
||||
[](https://join.slack.com/t/lazygit/shared_invite/zt-5bo2clzo-hB8ZTVN5dWUCqj5QFiQVLA)
|
||||
|
||||
## Donate
|
||||
|
||||
|
||||
+36
-13
@@ -1,17 +1,18 @@
|
||||
# User Config:
|
||||
# User Config
|
||||
|
||||
Default path for the config file:
|
||||
|
||||
* Linux: `~/.config/jesseduffield/lazygit/config.yml`
|
||||
* MacOS: `~/Library/Application Support/jesseduffield/lazygit/config.yml`
|
||||
|
||||
## Default:
|
||||
## Default
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
# stuff relating to the UI
|
||||
scrollHeight: 2 # how many lines you scroll by
|
||||
scrollPastBottom: true # enable scrolling past the bottom
|
||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
@@ -28,6 +29,9 @@ Default path for the config file:
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
@@ -79,6 +83,8 @@ Default path for the config file:
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
@@ -124,6 +130,7 @@ Default path for the config file:
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
resetCherryPick: '<c-R>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
@@ -133,33 +140,32 @@ Default path for the config file:
|
||||
toggleDragSelect-alt: 'V'
|
||||
toggleSelectHunk: 'a'
|
||||
pickBothHunks: 'b'
|
||||
undo: 'z'
|
||||
```
|
||||
|
||||
## Platform Defaults:
|
||||
## Platform Defaults
|
||||
|
||||
### Windows:
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||
```
|
||||
|
||||
### Linux:
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
```
|
||||
|
||||
### OSX:
|
||||
### OSX
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Recommended Config Values:
|
||||
### Recommended Config Values
|
||||
|
||||
for users of VSCode
|
||||
|
||||
@@ -168,7 +174,7 @@ for users of VSCode
|
||||
openCommand: 'code -r {{filename}}'
|
||||
```
|
||||
|
||||
## Color Attributes:
|
||||
## Color Attributes
|
||||
|
||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||
The available attributes are:
|
||||
@@ -186,7 +192,7 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme:
|
||||
## Light terminal theme
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
@@ -203,15 +209,16 @@ If you have issues with a light terminal theme where you can't read / see the te
|
||||
- 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)
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
#### Example Keybindings For Colemak Users:
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
@@ -238,3 +245,19 @@ For all possible keybinding options, check [Custom_Keybinding.md](https://github
|
||||
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,50 +1,31 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Global
|
||||
## Global Keybindings
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: refres
|
||||
<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>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
## Branches 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
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>s</kbd>: stash canges
|
||||
<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>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
@@ -53,14 +34,70 @@
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>n</kbd>: new 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>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## 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>
|
||||
<kbd>s</kbd>: squash down
|
||||
@@ -81,60 +118,43 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<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>
|
||||
|
||||
## Stash
|
||||
## Commits Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
## 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
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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>space</kbd>: stage line
|
||||
<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>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commit chages
|
||||
<kbd>W</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>s</kbd>: stash changes
|
||||
<kbd>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
@@ -146,3 +166,71 @@
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd> ̄</kbd>: scroll down (fn+up)
|
||||
<kbd>¦</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Main Panel (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Menu Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close menu
|
||||
<kbd>q</kbd>: close menu
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
@@ -1,46 +1,31 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: verversen
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>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>
|
||||
|
||||
## Status
|
||||
## Branches Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>s</kbd>: wissel naar een recente repo
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>S</kbd>: stash-bestanden
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>t</kbd>: bewerkingen toevoegen
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
@@ -51,10 +36,68 @@
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## 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>
|
||||
<kbd>s</kbd>: squash beneden
|
||||
@@ -75,39 +118,43 @@
|
||||
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<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>
|
||||
|
||||
## Stash
|
||||
## Commits Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
## 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>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>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Hoofd (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage lijn
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd (Merging)
|
||||
## Hoofd Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
@@ -120,9 +167,70 @@
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
</pre>
|
||||
|
||||
## Hoofd (Normaal)
|
||||
## Hoofd Panel (Normaal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
|
||||
<kbd> ̄</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>¦</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: exit line-by-line mode
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>tab</kbd>: switch to other panel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
</pre>
|
||||
|
||||
## Menu Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close menu
|
||||
<kbd>q</kbd>: close menu
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Stash Panel
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Status Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
</pre>
|
||||
|
||||
@@ -1,46 +1,31 @@
|
||||
# Lazygit menu
|
||||
# Lazygit Keybindings
|
||||
|
||||
## Globalne
|
||||
|
||||
<pre>
|
||||
<kbd>pgup</kbd>: scroll up main panel (fn+up)
|
||||
<kbd>pgdown</kbd>: scroll down main panel (fn+down)
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>ctrl+p</kbd>: view custom patch options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: odśwież
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>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>
|
||||
|
||||
## Status
|
||||
## Gałęzie Panel
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>S</kbd>: przechowaj pliki
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>t</kbd>: dodaj łatkę
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Gałęzie
|
||||
## Gałęzie Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
@@ -51,10 +36,68 @@
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## 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>
|
||||
<kbd>s</kbd>: ściśnij w dół
|
||||
@@ -75,46 +118,43 @@
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
<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>
|
||||
|
||||
## Schowek
|
||||
## Commity Panel (Reflog Tab)
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
<kbd>space</kbd>: checkout commit
|
||||
<kbd>g</kbd>: view reset options
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
## Pliki Panel
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<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>S</kbd>: view stash options
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: zatwierdź linię
|
||||
<kbd>a</kbd>: zatwierdź kawałek
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
## Main Panel (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
@@ -124,5 +164,73 @@
|
||||
<kbd>►</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select top 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>
|
||||
|
||||
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
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
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/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-errors/errors v1.0.1
|
||||
@@ -11,9 +12,7 @@ require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed
|
||||
github.com/jesseduffield/pty v1.2.1
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 // indirect
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
|
||||
@@ -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/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.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/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.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532 h1:V1Lk2rm5/p27NjnlF2ezzkxDaisHNcveMNueSD7RYgs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191116013947-b13bda319532/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351 h1:+sSqd2YotacWt+1MNRN8ZmXnYoiJeblZeptzKiHIyv0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200112025325-6c933915c351/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039 h1:CVhilJ8ZdN7GmAI+fbH9829Cp/8hbK7Lijbd4VaNgo0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131125953-f679540a7039/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac h1:vp7I0RpFq4L46nFA9QQokzhFgr68LRGtwDO9xfq4F+A=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200131131454-a319843434ac/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5 h1:tE0w3tuL/bj1o5VMhjjE0ep6i7Fva+RYjKcMFcniJEY=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200201013258-57fdcf23edc5/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/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/gocui v0.3.1-0.20200309001002-7765949e1c8a h1:JSORQue6V4bMppr22dtUuYX+w79cgupo66PcGZ9ijlU=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200309001002-7765949e1c8a/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9 h1:iBBk1lhFwjwJw//J2m1yyz9S368GeXQTpMVACTyQMh0=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20200130214842-1d31d1faa3c9/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -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/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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -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.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -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/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
@@ -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/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||
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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
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-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-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 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-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
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-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-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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
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 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -20,11 +19,6 @@ var (
|
||||
buildSource = "unknown"
|
||||
)
|
||||
|
||||
func projectPath(path string) string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
return filepath.FromSlash(gopath + "/src/github.com/jesseduffield/lazygit/" + path)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||
|
||||
|
||||
@@ -3,8 +3,12 @@ package commands
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
Name string
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
Name string
|
||||
// the displayname is something like '(HEAD detached at 123asdf)', whereas in that case the name would be '123asdf'
|
||||
DisplayName string
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
UpstreamName string
|
||||
Head bool
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
)
|
||||
|
||||
// context:
|
||||
@@ -36,113 +34,108 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchLis
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
||||
branchName, err := b.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
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()
|
||||
func (b *BranchListBuilder) obtainBranches() []*Branch {
|
||||
cmdStr := `git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`
|
||||
output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bIter.ForEach(func(b *plumbing.Reference) error {
|
||||
name := b.Name().Short()
|
||||
branches = append(branches, &Branch{Name: name})
|
||||
return nil
|
||||
})
|
||||
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
outputLines := strings.Split(trimmedOutput, "\n")
|
||||
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
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*Branch, included bool) []*Branch {
|
||||
for _, newBranch := range newBranches {
|
||||
if included == branchIncluded(newBranch.Name, existingBranches) {
|
||||
finalBranches = append(finalBranches, newBranch)
|
||||
}
|
||||
}
|
||||
return finalBranches
|
||||
}
|
||||
|
||||
func sanitisedReflogName(reflogBranch *Branch, safeBranches []*Branch) string {
|
||||
for _, safeBranch := range safeBranches {
|
||||
if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) {
|
||||
return safeBranch.Name
|
||||
}
|
||||
}
|
||||
return reflogBranch.Name
|
||||
}
|
||||
|
||||
// Build the list of branches for the current repo
|
||||
func (b *BranchListBuilder) Build() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
head := b.obtainCurrentBranch()
|
||||
safeBranches := b.obtainSafeBranches()
|
||||
branches := b.obtainBranches()
|
||||
|
||||
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 = b.appendNewBranches(branches, safeBranches, branches, false)
|
||||
branches = append(branchesWithRecency, branches...)
|
||||
|
||||
if len(branches) == 0 || branches[0].Name != head.Name {
|
||||
branches = append([]*Branch{head}, branches...)
|
||||
foundHead := false
|
||||
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
|
||||
}
|
||||
|
||||
func branchIncluded(branchName string, branches []*Branch) bool {
|
||||
for _, existingBranch := range branches {
|
||||
if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func uniqueByName(branches []*Branch) []*Branch {
|
||||
finalBranches := make([]*Branch, 0)
|
||||
for _, branch := range branches {
|
||||
if branchIncluded(branch.Name, finalBranches) {
|
||||
continue
|
||||
}
|
||||
finalBranches = append(finalBranches, branch)
|
||||
}
|
||||
return finalBranches
|
||||
}
|
||||
|
||||
// A line will have the form '10 days ago master' so we need to strip out the
|
||||
// useful information from that into timeNumber, timeUnit, and branchName
|
||||
func branchInfoFromLine(line string) (string, string) {
|
||||
@@ -172,3 +165,30 @@ func abbreviatedTimeUnit(timeUnit string) string {
|
||||
}
|
||||
return timeUnitMap[timeUnit]
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
// if we directly put this string in RunCommandWithOutput the compiler complains because it thinks it's a format string
|
||||
unescaped := "git reflog --date=relative --pretty='%gd|%gs' --grep-reflog='checkout: moving' HEAD"
|
||||
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput(unescaped)
|
||||
if err != nil {
|
||||
return branches
|
||||
}
|
||||
|
||||
branchNameMap := map[string]bool{}
|
||||
|
||||
branchLines := utils.SplitLines(rawString)
|
||||
for _, line := range branchLines {
|
||||
recency, branchName := branchInfoFromLine(line)
|
||||
if branchName == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := branchNameMap[branchName]; ok {
|
||||
continue
|
||||
}
|
||||
branchNameMap[branchName] = true
|
||||
branch := &Branch{Name: branchName, Recency: recency}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return branches
|
||||
}
|
||||
|
||||
+15
-10
@@ -2,14 +2,19 @@ package commands
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
Name string
|
||||
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||
DisplayString string
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
||||
Tags []string
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
Author string
|
||||
Date string
|
||||
Sha string
|
||||
Name string
|
||||
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Tags []string
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
Author string
|
||||
Date string
|
||||
}
|
||||
|
||||
func (c *Commit) ShortSha() string {
|
||||
if len(c.Sha) < 8 {
|
||||
return c.Sha
|
||||
}
|
||||
return c.Sha[:8]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -70,13 +71,12 @@ func (c *CommitListBuilder) extractCommitFromLine(line string) *Commit {
|
||||
}
|
||||
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
Name: message,
|
||||
DisplayString: line,
|
||||
Tags: tags,
|
||||
ExtraInfo: extraInfo,
|
||||
Date: date,
|
||||
Author: author,
|
||||
Sha: sha,
|
||||
Name: message,
|
||||
Tags: tags,
|
||||
ExtraInfo: extraInfo,
|
||||
Date: date,
|
||||
Author: author,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +100,20 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits()
|
||||
log := c.getLog(limit)
|
||||
cmd := c.getLogCmd(limit)
|
||||
|
||||
// now we can split it up and turn it into commits
|
||||
for _, line := range utils.SplitLines(log) {
|
||||
err = RunLineOutputCmd(cmd, func(line string) (bool, error) {
|
||||
commit := c.extractCommitFromLine(line)
|
||||
_, unpushed := unpushedCommits[commit.Sha[:8]]
|
||||
_, unpushed := unpushedCommits[commit.ShortSha()]
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, commit)
|
||||
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rebaseMode != "" {
|
||||
currentCommit := commits[len(rebasingCommits)]
|
||||
blue := color.New(color.FgYellow)
|
||||
@@ -121,11 +126,6 @@ func (c *CommitListBuilder) GetCommits(limit bool) ([]*Commit, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commits, err = c.setCommitCherryPickStatuses(commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, commit := range commits {
|
||||
for _, entry := range c.DiffEntries {
|
||||
if entry.Sha == commit.Sha {
|
||||
@@ -267,19 +267,8 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commi
|
||||
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) {
|
||||
currentBranch, err := c.GitCommand.CurrentBranchName()
|
||||
currentBranch, _, err := c.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -310,18 +299,11 @@ func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
}
|
||||
|
||||
// getLog gets the git log.
|
||||
func (c *CommitListBuilder) getLog(limit bool) string {
|
||||
func (c *CommitListBuilder) getLogCmd(limit bool) *exec.Cmd {
|
||||
limitFlag := ""
|
||||
if limit {
|
||||
limitFlag = "-30"
|
||||
limitFlag = "-300"
|
||||
}
|
||||
|
||||
result, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --oneline --pretty=format:\"%%H%s%%ar%s%%aN%s%%d%s%%s\" %s --abbrev=%d", SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, SEPARATION_CHAR, limitFlag, 20))
|
||||
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
}
|
||||
|
||||
return result
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"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
|
||||
|
||||
+117
-47
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -177,7 +178,7 @@ func stashEntryFromLine(line string, index int) *StashEntry {
|
||||
|
||||
// GetStashEntryDiff stash diff
|
||||
func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||
return fmt.Sprintf("git stash show -p --color stash@{%d}", index)
|
||||
return fmt.Sprintf("git stash show -p --color=%s stash@{%d}", c.colorArg(), index)
|
||||
}
|
||||
|
||||
// GetStatusFiles git status files
|
||||
@@ -321,28 +322,38 @@ func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCrede
|
||||
}
|
||||
|
||||
// ResetToCommit reset to commit
|
||||
func (c *GitCommand) ResetToCommit(sha string, strength string) error {
|
||||
return c.OSCommand.RunCommand("git reset --%s %s", strength, sha)
|
||||
func (c *GitCommand) ResetToCommit(sha string, strength string, options RunCommandOptions) error {
|
||||
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options)
|
||||
}
|
||||
|
||||
// NewBranch create new branch
|
||||
func (c *GitCommand) NewBranch(name string) error {
|
||||
return c.OSCommand.RunCommand("git checkout -b %s", name)
|
||||
func (c *GitCommand) NewBranch(name string, baseBranch string) error {
|
||||
return c.OSCommand.RunCommand("git checkout -b %s %s", name, baseBranch)
|
||||
}
|
||||
|
||||
// CurrentBranchName is a function.
|
||||
func (c *GitCommand) CurrentBranchName() (string, error) {
|
||||
// CurrentBranchName get the current branch name and displayname.
|
||||
// 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")
|
||||
if err != nil || branchName == "HEAD\n" {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git branch --contains")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
re := regexp.MustCompile(CurrentBranchNameRegex)
|
||||
match := re.FindStringSubmatch(output)
|
||||
branchName = match[1]
|
||||
if err == nil && branchName != "HEAD\n" {
|
||||
trimmedBranchName := strings.TrimSpace(branchName)
|
||||
return trimmedBranchName, trimmedBranchName, nil
|
||||
}
|
||||
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
|
||||
@@ -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
|
||||
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 := ""
|
||||
if force {
|
||||
if options.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`
|
||||
@@ -558,11 +574,11 @@ func (c *GitCommand) Ignore(filename string) error {
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
@@ -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 {
|
||||
cachedArg := ""
|
||||
trackedArg := "--"
|
||||
colorArg := "--color"
|
||||
colorArg := c.colorArg()
|
||||
split := strings.Split(file.Name, " -> ") // in case of a renamed file we get the new filename
|
||||
fileName := c.OSCommand.Quote(split[len(split)-1])
|
||||
if cached {
|
||||
@@ -601,10 +617,10 @@ func (c *GitCommand) DiffCmdStr(file *File, plain bool, cached bool) string {
|
||||
trackedArg = "--no-index /dev/null"
|
||||
}
|
||||
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 {
|
||||
@@ -865,7 +881,7 @@ func (c *GitCommand) CherryPickCommits(commits []*Commit) error {
|
||||
|
||||
// GetCommitFiles get the specified commit files
|
||||
func (c *GitCommand) GetCommitFiles(commitSha string, patchManager *PatchManager) ([]*CommitFile, error) {
|
||||
files, err := c.OSCommand.RunCommandWithOutput("git show --pretty= --name-only --no-renames %s", commitSha)
|
||||
files, err := c.OSCommand.RunCommandWithOutput("git diff-tree --no-commit-id --name-only -r --no-renames %s", commitSha)
|
||||
if err != nil {
|
||||
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 {
|
||||
colorArg := "--color"
|
||||
colorArg := c.colorArg()
|
||||
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
|
||||
@@ -967,7 +983,7 @@ func (c *GitCommand) ResetSoft(ref string) error {
|
||||
|
||||
// DiffCommits show diff between commits
|
||||
func (c *GitCommand) DiffCommits(sha1, sha2 string) (string, error) {
|
||||
return c.OSCommand.RunCommandWithOutput("git diff --color --stat -p %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
|
||||
@@ -1104,27 +1120,81 @@ func (c *GitCommand) FetchRemote(remoteName string) error {
|
||||
return c.OSCommand.RunCommand("git fetch %s", remoteName)
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetReflogCommits() ([]*Commit, error) {
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git reflog --abbrev=20")
|
||||
// GetNewReflogCommits only returns the new reflog commits since the given lastReflogCommit
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
commits := make([]*Commit, len(lines))
|
||||
re := regexp.MustCompile(`(\w+).*HEAD@\{\d+\}: (.*)`)
|
||||
for i, line := range lines {
|
||||
match := re.FindStringSubmatch(line)
|
||||
if len(match) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
commits[i] = &Commit{
|
||||
Sha: match[1],
|
||||
Name: match[2],
|
||||
Status: "reflog",
|
||||
}
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (c *GitCommand) ConfiguredPager() string {
|
||||
if os.Getenv("GIT_PAGER") != "" {
|
||||
return os.Getenv("GIT_PAGER")
|
||||
}
|
||||
if os.Getenv("PAGER") != "" {
|
||||
return os.Getenv("PAGER")
|
||||
}
|
||||
output, err := c.OSCommand.RunCommandWithOutput("git config --get-all core.pager")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
return strings.Split(trimmedOutput, "\n")[0]
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetPager(width int) string {
|
||||
useConfig := c.Config.GetUserConfig().GetBool("git.paging.useConfig")
|
||||
if useConfig {
|
||||
pager := c.ConfiguredPager()
|
||||
return strings.Split(pager, "| less")[0]
|
||||
}
|
||||
|
||||
templateValues := map[string]string{
|
||||
"columnWidth": strconv.Itoa(width/2 - 6),
|
||||
}
|
||||
|
||||
pagerTemplate := c.Config.GetUserConfig().GetString("git.paging.pager")
|
||||
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
|
||||
}
|
||||
|
||||
func (c *GitCommand) colorArg() string {
|
||||
return c.Config.GetUserConfig().GetString("git.paging.colorArg")
|
||||
}
|
||||
|
||||
func (c *GitCommand) RenameBranch(oldName string, newName string) error {
|
||||
return c.OSCommand.RunCommand("git branch --move %s %s", oldName, newName)
|
||||
}
|
||||
|
||||
+44
-19
@@ -628,7 +628,7 @@ func TestGitCommandResetToCommit(t *testing.T) {
|
||||
return exec.Command("echo")
|
||||
}
|
||||
|
||||
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard"))
|
||||
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", RunCommandOptions{}))
|
||||
}
|
||||
|
||||
// TestGitCommandNewBranch is a function.
|
||||
@@ -636,12 +636,12 @@ func TestGitCommandNewBranch(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"checkout", "-b", "test"}, args)
|
||||
assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
|
||||
assert.NoError(t, gitCmd.NewBranch("test"))
|
||||
assert.NoError(t, gitCmd.NewBranch("test", "master"))
|
||||
}
|
||||
|
||||
// TestGitCommandDeleteBranch is a function.
|
||||
@@ -1439,7 +1439,7 @@ func TestGitCommandCheckout(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
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.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test"}, args)
|
||||
assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
}
|
||||
@@ -1473,7 +1473,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"Default case",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--stat", "-p", "--color", "--", "test.txt"}, args)
|
||||
assert.EqualValues(t, []string{"diff", "--color=", "--", "test.txt"}, args)
|
||||
|
||||
return exec.Command("echo")
|
||||
},
|
||||
@@ -1489,7 +1489,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"cached",
|
||||
func(cmd string, args ...string) *exec.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")
|
||||
},
|
||||
@@ -1505,7 +1505,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"plain",
|
||||
func(cmd string, args ...string) *exec.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")
|
||||
},
|
||||
@@ -1521,7 +1521,7 @@ func TestGitCommandDiff(t *testing.T) {
|
||||
"File not tracked and file has no staged changes",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"diff", "--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")
|
||||
},
|
||||
@@ -1549,7 +1549,7 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string, error)
|
||||
test func(string, string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
@@ -1559,9 +1559,10 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
|
||||
assert.Equal(t, "git", cmd)
|
||||
return exec.Command("echo", "master")
|
||||
},
|
||||
func(output string, err error) {
|
||||
func(name string, displayname string, err error) {
|
||||
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
|
||||
},
|
||||
func(output string, err error) {
|
||||
func(name string, displayname string, err error) {
|
||||
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)
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(output string, err error) {
|
||||
func(name string, displayname string, err error) {
|
||||
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",
|
||||
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",
|
||||
},
|
||||
}),
|
||||
@@ -1907,7 +1932,7 @@ func TestGitCommandGetCommitFiles(t *testing.T) {
|
||||
"123456",
|
||||
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'",
|
||||
},
|
||||
}),
|
||||
@@ -2130,7 +2155,7 @@ func TestGitCommandSkipEditorCommand(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
cmd.RunSkipEditorCommand("true")
|
||||
_ = cmd.RunSkipEditorCommand("true")
|
||||
}
|
||||
|
||||
func TestFindDotGitDir(t *testing.T) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -64,6 +65,22 @@ func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.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
|
||||
// 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
|
||||
@@ -401,3 +418,39 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Kill(cmd *exec.Cmd) error {
|
||||
if cmd.Process == nil {
|
||||
// somebody got to it before we were able to, poor bastard
|
||||
return nil
|
||||
}
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
)
|
||||
|
||||
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||
@@ -26,27 +27,63 @@ type RepoInformation struct {
|
||||
Repository string
|
||||
}
|
||||
|
||||
func getServices() []*Service {
|
||||
return []*Service{
|
||||
{
|
||||
Name: "github.com",
|
||||
PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
|
||||
},
|
||||
{
|
||||
Name: "bitbucket.org",
|
||||
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
|
||||
},
|
||||
{
|
||||
Name: "gitlab.com",
|
||||
PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
|
||||
},
|
||||
// NewService builds a Service based on the host type
|
||||
func NewService(typeName string, repositoryDomain string, siteDomain string) *Service {
|
||||
var service *Service
|
||||
|
||||
switch typeName {
|
||||
case "github":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/compare/%s?expand=1"),
|
||||
}
|
||||
case "bitbucket":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/pull-requests/new?source=%s&t=1"),
|
||||
}
|
||||
case "gitlab":
|
||||
service = &Service{
|
||||
Name: repositoryDomain,
|
||||
PullRequestURL: fmt.Sprintf("https://%s%s", siteDomain, "/%s/%s/merge_requests/new?merge_request[source_branch]=%s"),
|
||||
}
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func getServices(config config.AppConfigurer) []*Service {
|
||||
services := []*Service{
|
||||
NewService("github", "github.com", "github.com"),
|
||||
NewService("bitbucket", "bitbucket.org", "bitbucket.org"),
|
||||
NewService("gitlab", "gitlab.com", "gitlab.com"),
|
||||
}
|
||||
|
||||
configServices := config.GetUserConfig().GetStringMapString("services")
|
||||
|
||||
for repoDomain, typeAndDomain := range configServices {
|
||||
splitData := strings.Split(typeAndDomain, ":")
|
||||
if len(splitData) != 2 {
|
||||
// TODO log this misconfiguration
|
||||
continue
|
||||
}
|
||||
|
||||
service := NewService(splitData[0], repoDomain, splitData[1])
|
||||
if service == nil {
|
||||
// TODO log this unsupported service
|
||||
continue
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
// NewPullRequest creates new instance of PullRequest
|
||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||
return &PullRequest{
|
||||
GitServices: getServices(),
|
||||
GitServices: getServices(gitCommand.Config),
|
||||
GitCommand: gitCommand,
|
||||
}
|
||||
}
|
||||
@@ -85,7 +122,7 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
if isHTTP {
|
||||
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")
|
||||
|
||||
return &RepoInformation{
|
||||
@@ -96,8 +133,8 @@ func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
|
||||
tmpSplit := strings.Split(url, ":")
|
||||
splits := strings.Split(tmpSplit[1], "/")
|
||||
owner := splits[0]
|
||||
repo := strings.TrimSuffix(splits[1], ".git")
|
||||
owner := strings.Join(splits[0:len(splits)-1], "/")
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
Owner: owner,
|
||||
|
||||
@@ -147,6 +147,13 @@ func TestCreatePullRequest(t *testing.T) {
|
||||
gitCommand := NewDummyGitCommand()
|
||||
gitCommand.OSCommand.command = s.command
|
||||
gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
|
||||
gitCommand.Config.GetUserConfig().Set("services", map[string]string{
|
||||
// valid configuration for a custom service URL
|
||||
"git.work.com": "gitlab:code.work.com",
|
||||
// invalid configurations for a custom service URL
|
||||
"invalid.work.com": "noservice:invalid.work.com",
|
||||
"noservice.work.com": "noservice.work.com",
|
||||
})
|
||||
dummyPullRequest := NewPullRequest(gitCommand)
|
||||
s.test(dummyPullRequest.Create(s.branch))
|
||||
})
|
||||
|
||||
@@ -244,6 +244,7 @@ func GetDefaultConfig() []byte {
|
||||
scrollPastBottom: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
sidePanelWidth: 0.3333
|
||||
theme:
|
||||
lightTheme: false
|
||||
activeBorderColor:
|
||||
@@ -258,6 +259,9 @@ func GetDefaultConfig() []byte {
|
||||
commitLength:
|
||||
show: true
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
useConfig: false
|
||||
merging:
|
||||
manualCommit: false
|
||||
skipHookPrefix: 'WIP'
|
||||
@@ -310,6 +314,8 @@ keybinding:
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
@@ -330,6 +336,7 @@ keybinding:
|
||||
checkoutBranchByName: 'c'
|
||||
forceCheckoutBranch: 'F'
|
||||
rebaseBranch: 'r'
|
||||
renameBranch: 'R'
|
||||
mergeIntoCurrentBranch: 'M'
|
||||
viewGitFlowOptions: 'i'
|
||||
fastForward: 'f'
|
||||
@@ -355,6 +362,7 @@ keybinding:
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
resetCherryPick: '<c-R>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
@@ -364,7 +372,6 @@ keybinding:
|
||||
toggleDragSelect-alt: 'V'
|
||||
toggleSelectHunk: 'a'
|
||||
pickBothHunks: 'b'
|
||||
undo: 'z'
|
||||
`)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,7 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
|
||||
gui.Log.Warn(err)
|
||||
}
|
||||
gui.renderString(gui.g, "appStatus", appStatus)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
+122
-77
@@ -40,12 +40,7 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Branches.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
|
||||
@@ -56,24 +51,6 @@ func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
||||
return gui.newTask("branches", func(stop chan struct{}) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
branchesView := gui.getBranchesView()
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.currentViewName() == "branches", gui.State.Panels.Branches.SelectedLine)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// gui.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a state.Branches array to pick the current branch from
|
||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
@@ -108,9 +85,8 @@ func (gui *Gui) renderLocalBranchesWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(branchesView, displayStrings)
|
||||
if gui.g.CurrentView() == branchesView {
|
||||
if err := gui.handleBranchSelect(gui.g, branchesView); err != nil {
|
||||
return err
|
||||
@@ -130,7 +106,7 @@ func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
return gui.handleCheckoutRef(branch.Name)
|
||||
return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -160,55 +136,75 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.Tr.SLocalize("SureForceCheckout")
|
||||
title := gui.Tr.SLocalize("ForceCheckoutBranch")
|
||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Checkout(branch.Name, true); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
if err := gui.GitCommand.Checkout(branch.Name, commands.CheckoutOptions{Force: true}); err != nil {
|
||||
_ = gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutRef(ref string) error {
|
||||
if err := gui.GitCommand.Checkout(ref, false); err != nil {
|
||||
// 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
|
||||
type handleCheckoutRefOptions struct {
|
||||
WaitingStatus string
|
||||
EnvVars []string
|
||||
}
|
||||
|
||||
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, 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
|
||||
}
|
||||
func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error {
|
||||
waitingStatus := options.WaitingStatus
|
||||
if waitingStatus == "" {
|
||||
waitingStatus = gui.Tr.SLocalize("CheckingOutStatus")
|
||||
}
|
||||
|
||||
gui.State.Panels.Branches.SelectedLine = 0
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
cmdOptions := commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
|
||||
|
||||
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 {
|
||||
gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutRef(gui.trimmedContent(v))
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCheckoutRef(gui.trimmedContent(v), handleCheckoutRefOptions{})
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||
@@ -220,31 +216,31 @@ func (gui *Gui) getCheckedOutBranch() *commands.Branch {
|
||||
}
|
||||
|
||||
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(
|
||||
"NewBranchNameBranchOff",
|
||||
Teml{
|
||||
"branchName": branch.Name,
|
||||
},
|
||||
)
|
||||
gui.createPromptPanel(g, v, message, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.NewBranch(gui.trimmedContent(v)); err != nil {
|
||||
return gui.createPromptPanel(g, v, message, "", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.NewBranch(gui.trimmedContent(v), branch.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshSidePanels(g); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshSidePanels(g)
|
||||
return gui.handleBranchSelect(g, v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.deleteBranch(g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.deleteBranch(g, v, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
selectedBranch := gui.getSelectedBranch()
|
||||
if selectedBranch == nil {
|
||||
@@ -378,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 {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
_ = gui.refreshBranches(gui.g)
|
||||
}
|
||||
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
@@ -397,7 +393,9 @@ func (gui *Gui) onBranchesTabClick(tabIndex int) error {
|
||||
func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||
branchesView := gui.getBranchesView()
|
||||
branchesView.Context = context
|
||||
gui.onSearchEscape()
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"local-branches": 0,
|
||||
@@ -408,7 +406,13 @@ func (gui *Gui) switchBranchesPanelContext(context string) error {
|
||||
|
||||
branchesView.TabIndex = contextTabIndexMap[context]
|
||||
|
||||
switch context {
|
||||
return gui.refreshBranchesViewWithSelection()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshBranchesViewWithSelection() error {
|
||||
branchesView := gui.getBranchesView()
|
||||
|
||||
switch branchesView.Context {
|
||||
case "local-branches":
|
||||
return gui.renderLocalBranchesWithSelection()
|
||||
case "remotes":
|
||||
@@ -458,3 +462,44 @@ func (gui *Gui) onBranchesPanelSearchSelect(selectedLine int) error {
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
||||
@@ -36,21 +36,20 @@ func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.CommitFiles.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.ShowCommitFileCmdStr(commitFile.Sha, commitFile.Name, false),
|
||||
)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -132,7 +131,8 @@ func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
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 {
|
||||
@@ -187,7 +187,8 @@ func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
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 {
|
||||
|
||||
@@ -52,7 +52,7 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
_, _ = g.SetViewOnBottom("commitMessage")
|
||||
return gui.switchFocus(g, v, gui.getFilesView())
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
return gui.renderString(g, "options", message)
|
||||
gui.renderString(g, "options", message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
||||
|
||||
+31
-70
@@ -3,8 +3,6 @@ package gui
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
@@ -37,7 +35,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
state := gui.State.Panels.Commits
|
||||
if state.SelectedLine > 20 && state.LimitCommits {
|
||||
if state.SelectedLine > 290 && state.LimitCommits {
|
||||
state.LimitCommits = false
|
||||
go func() {
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
@@ -55,9 +53,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Commits.SelectedLine)
|
||||
|
||||
// if specific diff mode is on, don't show diff
|
||||
if gui.State.Panels.Commits.SpecificDiffMode {
|
||||
@@ -67,7 +63,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -77,7 +73,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
// 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 {
|
||||
return err
|
||||
@@ -121,28 +117,6 @@ func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, commitView, true, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
|
||||
}
|
||||
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, "mixed"); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.resetOrigin(commitView)
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, commitView)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
@@ -156,23 +130,12 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
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 {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: move to files panel
|
||||
func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool {
|
||||
for _, file := range files {
|
||||
if file.Tracked && file.HasUnstagedChanges {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -188,13 +151,12 @@ func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
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 {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -264,21 +226,6 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
// handleMoveTodoDown like handleMidRebaseCommand but for moving an item up in the todo list
|
||||
func (gui *Gui) handleMoveTodoDown(index int) (bool, error) {
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status != "rebasing" {
|
||||
return false, nil
|
||||
}
|
||||
if gui.State.Commits[index+1].Status != "rebasing" {
|
||||
return true, nil
|
||||
}
|
||||
if err := gui.GitCommand.MoveTodoDown(index); err != nil {
|
||||
return true, gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
applied, err := gui.handleMidRebaseCommand("drop")
|
||||
if err != nil {
|
||||
@@ -404,14 +351,21 @@ func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
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) {
|
||||
// not super happy with modifying the state of the Commits array here
|
||||
// but the alternative would be very tricky
|
||||
gui.State.Commits[index].Copied = true
|
||||
commitShaMap := gui.cherryPickedCommitShaMap()
|
||||
commitShaMap[gui.State.Commits[index].Sha] = true
|
||||
|
||||
newCommits := []*commands.Commit{}
|
||||
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
|
||||
newCommits = append(newCommits, &commands.Commit{Name: commit.Name, Sha: commit.Sha})
|
||||
}
|
||||
@@ -421,13 +375,13 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
||||
}
|
||||
|
||||
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
|
||||
// if there are none, startIndex = 0
|
||||
startIndex := 0
|
||||
for index, commit := range gui.State.Commits[0:gui.State.Panels.Commits.SelectedLine] {
|
||||
if commit.Copied {
|
||||
if commitShaMap[commit.Sha] {
|
||||
startIndex = index
|
||||
}
|
||||
}
|
||||
@@ -438,7 +392,7 @@ func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.addCommitToCherryPickedCommits(index)
|
||||
}
|
||||
|
||||
return gui.refreshCommits(gui.g)
|
||||
return gui.renderBranchCommitsWithSelection()
|
||||
}
|
||||
|
||||
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
||||
@@ -504,7 +458,7 @@ func (gui *Gui) setDiffMode() {
|
||||
v.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
}
|
||||
|
||||
gui.refreshCommits(gui.g)
|
||||
_ = gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) {
|
||||
@@ -593,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.handleCheckoutRef(commit.Sha)
|
||||
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -601,7 +555,7 @@ func (gui *Gui) renderBranchCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.Commits, gui.State.ScreenMode != SCREEN_NORMAL, gui.cherryPickedCommitShaMap())
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "branch-commits" {
|
||||
if err := gui.handleCommitSelect(gui.g, commitsView); err != nil {
|
||||
@@ -623,7 +577,9 @@ func (gui *Gui) onCommitsTabClick(tabIndex int) error {
|
||||
func (gui *Gui) switchCommitsPanelContext(context string) error {
|
||||
commitsView := gui.getCommitsView()
|
||||
commitsView.Context = context
|
||||
gui.onSearchEscape()
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextTabIndexMap := map[string]int{
|
||||
"branch-commits": 0,
|
||||
@@ -693,3 +649,8 @@ func (gui *Gui) handleOpenSearchForCommitsPanel(g *gocui.Gui, v *gocui.View) err
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
g.DeleteKeybindings("confirmation")
|
||||
g.DeleteKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone)
|
||||
g.DeleteKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone)
|
||||
return g.DeleteView("confirmation")
|
||||
}
|
||||
|
||||
@@ -61,6 +62,9 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt s
|
||||
width, height := g.Size()
|
||||
panelWidth := 4 * width / 7
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||
if panelHeight > height*3/4 {
|
||||
panelHeight = height * 3 / 4
|
||||
}
|
||||
return width/2 - panelWidth/2,
|
||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
gui.renderString(g, "confirmation", prompt)
|
||||
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
|
||||
})
|
||||
return nil
|
||||
@@ -151,19 +153,14 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
||||
"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 {
|
||||
return err
|
||||
}
|
||||
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
|
||||
// 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,
|
||||
|
||||
@@ -16,5 +16,4 @@ func (gui *Gui) changeMainViewsContext(context string) {
|
||||
}
|
||||
|
||||
gui.State.MainContext = context
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error
|
||||
"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
|
||||
|
||||
@@ -23,11 +23,13 @@ type fileWatcher struct {
|
||||
}
|
||||
|
||||
func NewFileWatcher(log *logrus.Entry) *fileWatcher {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
log.Error(err)
|
||||
// TODO: get this going again, and ensure we don't see any crashes from it
|
||||
return &fileWatcher{
|
||||
Disabled: true,
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return &fileWatcher{
|
||||
|
||||
+26
-38
@@ -37,9 +37,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
gui.getFilesView().FocusPoint(0, gui.State.Panels.Files.SelectedLine)
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
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")
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, true)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("secondary", cmd); err != nil {
|
||||
if err := gui.newPtyTask("secondary", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -76,7 +74,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
|
||||
|
||||
cmdStr := gui.GitCommand.DiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
|
||||
if err := gui.newCmdTask("main", cmd); err != nil {
|
||||
if err := gui.newPtyTask("main", cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -188,9 +186,12 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
if file.HasUnstagedChanges {
|
||||
gui.GitCommand.StageFile(file.Name)
|
||||
err = gui.GitCommand.StageFile(file.Name)
|
||||
} 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 {
|
||||
@@ -268,9 +269,7 @@ func (gui *Gui) handleWIPCommitPress(g *gocui.Gui, filesView *gocui.View) error
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
|
||||
}
|
||||
|
||||
if err := gui.renderString(g, "commitMessage", skipHookPreifx); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.renderString(g, "commitMessage", skipHookPreifx)
|
||||
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,8 +283,14 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
}
|
||||
commitMessageView := gui.getCommitMessageView()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
g.SetViewOnTop("commitMessage")
|
||||
gui.switchFocus(g, filesView, commitMessageView)
|
||||
if _, err := g.SetViewOnTop("commitMessage"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.switchFocus(g, filesView, commitMessageView); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.RenderCommitLength()
|
||||
return nil
|
||||
})
|
||||
@@ -394,24 +399,20 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
|
||||
func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
// if we have no upstream branch we need to set that first
|
||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
currentBranchName, err := gui.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pullables == "?" {
|
||||
currentBranch := gui.currentBranch()
|
||||
if currentBranch.Pullables == "?" {
|
||||
// see if we have this branch in our config with an upstream
|
||||
conf, err := gui.GitCommand.Repo.Config()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
for branchName, branch := range conf.Branches {
|
||||
if branchName == currentBranchName {
|
||||
if branchName == currentBranch.Name {
|
||||
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin/"+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)
|
||||
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
|
||||
errorMessage := err.Error()
|
||||
@@ -462,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 {
|
||||
// if we have pullables we'll ask if the user wants to force push
|
||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
currentBranchName, err := gui.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentBranch := gui.currentBranch()
|
||||
|
||||
if pullables == "?" {
|
||||
if currentBranch.Pullables == "?" {
|
||||
// see if we have this branch in our config with an upstream
|
||||
conf, err := gui.GitCommand.Repo.Config()
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
for branchName, branch := range conf.Branches {
|
||||
if branchName == currentBranchName {
|
||||
if branchName == currentBranch.Name {
|
||||
return gui.pushWithForceFlag(g, v, false, "", fmt.Sprintf("%s %s", branch.Remote, branchName))
|
||||
}
|
||||
}
|
||||
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("EnterUpstream"), "origin "+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), "")
|
||||
})
|
||||
} else if pullables == "0" {
|
||||
} else if currentBranch.Pullables == "0" {
|
||||
return gui.pushWithForceFlag(g, v, false, "", "")
|
||||
}
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -509,15 +506,6 @@ func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.AbortMerge(); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("MergeAborted"))
|
||||
gui.refreshStatus(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) openFile(filename string) error {
|
||||
if err := gui.OSCommand.OpenFile(filename); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
|
||||
+64
-27
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
@@ -81,7 +80,7 @@ type Gui struct {
|
||||
GitCommand *commands.GitCommand
|
||||
OSCommand *commands.OSCommand
|
||||
SubProcess *exec.Cmd
|
||||
State guiState
|
||||
State *guiState
|
||||
Config config.AppConfigurer
|
||||
Tr *i18n.Localizer
|
||||
Errors SentinelErrors
|
||||
@@ -211,6 +210,10 @@ type guiState struct {
|
||||
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
|
||||
@@ -218,7 +221,7 @@ type guiState struct {
|
||||
// NewGui builds a new gui handler
|
||||
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
|
||||
|
||||
initialState := guiState{
|
||||
initialState := &guiState{
|
||||
Files: make([]*commands.File, 0),
|
||||
PreviousView: "files",
|
||||
Commits: make([]*commands.Commit, 0),
|
||||
@@ -247,6 +250,7 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
},
|
||||
ScreenMode: SCREEN_NORMAL,
|
||||
SideView: nil,
|
||||
Ptmx: nil,
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
@@ -274,6 +278,10 @@ func (gui *Gui) nextScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
// same with branches
|
||||
if err := gui.refreshBranchesViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -284,6 +292,10 @@ func (gui *Gui) prevScreenMode(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.refreshCommitsViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
// same with branches
|
||||
if err := gui.refreshBranchesViewWithSelection(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -331,6 +343,20 @@ func (gui *Gui) scrollDownSecondary(g *gocui.Gui, v *gocui.View) error {
|
||||
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 {
|
||||
return gui.refreshSidePanels(g)
|
||||
}
|
||||
@@ -377,15 +403,11 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
if v.IsSearching() && newView.Name() != "search" {
|
||||
gui.onSearchEscape()
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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
|
||||
displayStrings := presentation.GetBranchListDisplayStrings(gui.State.Branches, false, -1)
|
||||
gui.renderDisplayStrings(gui.getBranchesView(), displayStrings)
|
||||
}
|
||||
case "main":
|
||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||
gui.changeMainViewsContext("normal")
|
||||
@@ -487,6 +509,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
|
||||
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
|
||||
minimumWidth := 10
|
||||
@@ -514,13 +539,15 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
g.DeleteView("limit")
|
||||
_ = g.DeleteView("limit")
|
||||
|
||||
sidePanelWidthRatio := gui.Config.GetUserConfig().GetFloat64("gui.sidePanelWidth")
|
||||
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
var leftSideWidth int
|
||||
switch gui.State.ScreenMode {
|
||||
case SCREEN_NORMAL:
|
||||
leftSideWidth = width / 3
|
||||
leftSideWidth = int(float64(width) * sidePanelWidthRatio)
|
||||
case SCREEN_HALF:
|
||||
leftSideWidth = width / 2
|
||||
case SCREEN_FULL:
|
||||
@@ -532,7 +559,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
panelSplitX := width - 1
|
||||
mainPanelLeft := leftSideWidth + 1
|
||||
mainPanelRight := width - 1
|
||||
secondaryPanelLeft := width - 1
|
||||
@@ -541,7 +567,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if gui.State.SplitMainPanel {
|
||||
if gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainPanelLeft = 0
|
||||
panelSplitX = width/2 - 4
|
||||
panelSplitX := width/2 - 4
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
} else if width < 220 {
|
||||
@@ -552,7 +578,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
units := 5
|
||||
leftSideWidth = width / units
|
||||
mainPanelLeft = leftSideWidth + 1
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
panelSplitX := (1 + ((units - 1) / 2)) * width / units
|
||||
mainPanelRight = panelSplitX
|
||||
secondaryPanelLeft = panelSplitX + 1
|
||||
}
|
||||
@@ -687,7 +713,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
_, _ = g.SetViewOnBottom("commitMessage")
|
||||
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
||||
commitMessageView.FgColor = textColor
|
||||
commitMessageView.Editable = true
|
||||
@@ -752,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" {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
if err := gui.renderString(g, "information", information); err != nil {
|
||||
return err
|
||||
}
|
||||
informationView.BgColor = gocui.ColorDefault
|
||||
informationView.FgColor = gocui.ColorGreen
|
||||
informationView.Frame = false
|
||||
gui.renderString(g, "information", information)
|
||||
|
||||
// doing this here because it'll only happen once
|
||||
if err := gui.onInitialViewsCreation(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if gui.State.OldInformation != information {
|
||||
gui.setViewContent(g, informationView, information)
|
||||
gui.State.OldInformation = information
|
||||
}
|
||||
|
||||
if gui.g.CurrentView() == nil {
|
||||
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
||||
@@ -806,7 +835,14 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
continue
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -894,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.refreshStatus(g)
|
||||
_ = gui.refreshStatus(g)
|
||||
|
||||
return unamePassOpend, err
|
||||
}
|
||||
|
||||
@@ -903,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 %s %s", gui.getKeyDisplay("universal.prevBlock"), gui.getKeyDisplay("universal.nextBlock"), gui.getKeyDisplay("universal.prevItem"), gui.getKeyDisplay("universal.nextItem")): gui.Tr.SLocalize("navigate"),
|
||||
fmt.Sprintf("%s/%s", gui.getKeyDisplay("universal.return"), gui.getKeyDisplay("universal.quit")): gui.Tr.SLocalize("close"),
|
||||
fmt.Sprintf("%s", gui.getKeyDisplay("universal.optionMenu")): gui.Tr.SLocalize("menu"),
|
||||
gui.getKeyDisplay("universal.optionMenu"): gui.Tr.SLocalize("menu"),
|
||||
"1-5": gui.Tr.SLocalize("jump"),
|
||||
})
|
||||
}
|
||||
@@ -942,7 +979,7 @@ func (gui *Gui) startBackgroundFetch() {
|
||||
|
||||
// Run setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run() error {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
g, err := gocui.NewGui(gocui.Output256, OverlappingEdges)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+113
-50
@@ -162,15 +162,15 @@ func (gui *Gui) getKeyDisplay(name string) string {
|
||||
func GetKeyDisplay(key interface{}) string {
|
||||
keyInt := 0
|
||||
|
||||
switch key.(type) {
|
||||
switch key := key.(type) {
|
||||
case rune:
|
||||
keyInt = int(key.(rune))
|
||||
keyInt = int(key)
|
||||
case gocui.Key:
|
||||
value, ok := keyMapReversed[key.(gocui.Key)]
|
||||
value, ok := keyMapReversed[key]
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
keyInt = int(key.(gocui.Key))
|
||||
keyInt = int(key)
|
||||
}
|
||||
|
||||
return string(keyInt)
|
||||
@@ -225,6 +225,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpMain,
|
||||
Alternative: "fn+up",
|
||||
Description: gui.Tr.SLocalize("scrollUpMainPanel"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -232,6 +233,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
Alternative: "fn+down",
|
||||
Description: gui.Tr.SLocalize("scrollDownMainPanel"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -293,10 +295,11 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Description: gui.Tr.SLocalize("refresh"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.optionMenu"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateOptionsMenu,
|
||||
ViewName: "",
|
||||
Key: gui.getKey("universal.optionMenu"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateOptionsMenu,
|
||||
Description: gui.Tr.SLocalize("openMenu"),
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -310,6 +313,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
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",
|
||||
Key: gui.getKey("universal.edit"),
|
||||
@@ -566,6 +583,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleCreateResetToBranchMenu,
|
||||
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",
|
||||
Contexts: []string{"tags"},
|
||||
@@ -607,16 +632,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Key: gui.getKey("universal.nextTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleNextBranchesTab,
|
||||
ViewName: "branches",
|
||||
Key: gui.getKey("universal.nextTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleNextBranchesTab,
|
||||
Description: gui.Tr.SLocalize("nextTab"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
Key: gui.getKey("universal.prevTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePrevBranchesTab,
|
||||
ViewName: "branches",
|
||||
Key: gui.getKey("universal.prevTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePrevBranchesTab,
|
||||
Description: gui.Tr.SLocalize("prevTab"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
@@ -643,22 +670,25 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Description: gui.Tr.SLocalize("fetchRemote"),
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.nextTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleNextCommitsTab,
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.nextTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleNextCommitsTab,
|
||||
Description: gui.Tr.SLocalize("nextTab"),
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.prevTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePrevCommitsTab,
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.prevTab"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePrevCommitsTab,
|
||||
Description: gui.Tr.SLocalize("prevTab"),
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.startSearch"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleOpenSearchForCommitsPanel,
|
||||
ViewName: "commits",
|
||||
Key: gui.getKey("universal.startSearch"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleOpenSearchForCommitsPanel,
|
||||
Description: gui.Tr.SLocalize("startSearch"),
|
||||
},
|
||||
{
|
||||
ViewName: "commits",
|
||||
@@ -828,6 +858,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleTagCommit,
|
||||
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",
|
||||
Contexts: []string{"reflog-commits"},
|
||||
@@ -890,16 +928,18 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Handler: gui.handleCloseCredentialsView,
|
||||
},
|
||||
{
|
||||
ViewName: "menu",
|
||||
Key: gui.getKey("universal.return"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuClose,
|
||||
ViewName: "menu",
|
||||
Key: gui.getKey("universal.return"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuClose,
|
||||
Description: gui.Tr.SLocalize("closeMenu"),
|
||||
},
|
||||
{
|
||||
ViewName: "menu",
|
||||
Key: gui.getKey("universal.quit"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuClose,
|
||||
ViewName: "menu",
|
||||
Key: gui.getKey("universal.quit"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuClose,
|
||||
Description: gui.Tr.SLocalize("closeMenu"),
|
||||
},
|
||||
{
|
||||
ViewName: "information",
|
||||
@@ -1013,7 +1053,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{"staging"},
|
||||
Key: gui.getKey("universal.select"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStageSelection,
|
||||
Handler: gui.handleToggleStagedSelection,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
},
|
||||
{
|
||||
@@ -1135,16 +1175,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{"patch-building"},
|
||||
Key: gui.getKey("universal.select"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAddSelectionToPatch,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"patch-building"},
|
||||
Key: gui.getKey("universal.remove"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleRemoveSelectionFromPatch,
|
||||
Description: gui.Tr.SLocalize("ResetSelection"),
|
||||
Handler: gui.handleToggleSelectionForPatch,
|
||||
Description: gui.Tr.SLocalize("ToggleSelectionForPatch"),
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
@@ -1324,10 +1356,10 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{"merging"},
|
||||
Key: gui.getKey("main.undo"),
|
||||
Key: gui.getKey("universal.undo"),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePopFileSnapshot,
|
||||
Description: gui.Tr.SLocalize("Undo"),
|
||||
Description: gui.Tr.SLocalize("undo"),
|
||||
},
|
||||
{
|
||||
ViewName: "branches",
|
||||
@@ -1430,6 +1462,30 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
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"} {
|
||||
@@ -1460,7 +1516,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
|
||||
// we need a specific keybinding for the commits panel beacuse it usually lazyloads 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 {
|
||||
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
|
||||
@@ -220,6 +221,10 @@ func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCycleLine(1)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedCommitFileName() string {
|
||||
return gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainView() error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
@@ -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
|
||||
// how to get around this
|
||||
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)
|
||||
}
|
||||
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
||||
@@ -237,7 +242,8 @@ func (gui *Gui) refreshMainView() error {
|
||||
mainView.Wrap = false
|
||||
|
||||
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
|
||||
|
||||
@@ -17,7 +17,8 @@ type menuItem struct {
|
||||
// list panel functions
|
||||
|
||||
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
|
||||
|
||||
+13
-7
@@ -151,13 +151,19 @@ func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
||||
if err != nil {
|
||||
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()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
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"
|
||||
if gui.State.Panels.Merging.ConflictTop {
|
||||
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 {
|
||||
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")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -215,9 +223,7 @@ func (gui *Gui) refreshMergePanel() error {
|
||||
|
||||
mainView := gui.getMainView()
|
||||
mainView.Wrap = false
|
||||
if err := gui.setViewContent(gui.g, mainView, content); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.setViewContent(gui.g, mainView, content)
|
||||
gui.Log.Warn("scrolling to conflict")
|
||||
if err := gui.scrollToConflict(gui.g); err != nil {
|
||||
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"),
|
||||
gui.getKeyDisplay("universal.select"): gui.Tr.SLocalize("pickHunk"),
|
||||
gui.getKeyDisplay("main.pickBothHunks"): gui.Tr.SLocalize("pickBothHunks"),
|
||||
gui.getKeyDisplay("main.undo"): gui.Tr.SLocalize("undo"),
|
||||
gui.getKeyDisplay("universal.undo"): gui.Tr.SLocalize("undo"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
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
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
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)
|
||||
@@ -42,38 +44,25 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
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
|
||||
|
||||
toggleFunc := gui.GitCommand.PatchManager.AddFileLineRange
|
||||
filename := gui.getSelectedCommitFileName()
|
||||
includedLineIndices := gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.SelectedLineIdx)
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = gui.GitCommand.PatchManager.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
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)
|
||||
|
||||
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)
|
||||
toggleFunc(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
@@ -105,7 +94,8 @@ func (gui *Gui) refreshSecondaryPatchPanel() error {
|
||||
secondaryView.Wrap = false
|
||||
|
||||
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 {
|
||||
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",
|
||||
onPress: gui.handlePullPatchIntoWorkingTree,
|
||||
},
|
||||
{
|
||||
displayString: "apply patch",
|
||||
onPress: gui.handleApplyPatch,
|
||||
},
|
||||
{
|
||||
displayString: "reset patch",
|
||||
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 {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
return gui.refreshCommitFilesView()
|
||||
|
||||
@@ -10,25 +10,42 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetBranchListDisplayStrings(branches []*commands.Branch, isFocused bool, selectedLine int) [][]string {
|
||||
func GetBranchListDisplayStrings(branches []*commands.Branch, fullDescription bool) [][]string {
|
||||
lines := make([][]string, len(branches))
|
||||
|
||||
for i := range branches {
|
||||
showUpstreamDifferences := isFocused && i == selectedLine
|
||||
lines[i] = getBranchDisplayStrings(branches[i], showUpstreamDifferences)
|
||||
lines[i] = getBranchDisplayStrings(branches[i], fullDescription)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// getBranchDisplayStrings returns the display string of branch
|
||||
func getBranchDisplayStrings(b *commands.Branch, showUpstreamDifferences bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, GetBranchColor(b.Name))
|
||||
if showUpstreamDifferences && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
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)
|
||||
}
|
||||
|
||||
return []string{b.Recency, displayName}
|
||||
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
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool) [][]string {
|
||||
func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription bool, cherryPickedCommitShaMap map[string]bool) [][]string {
|
||||
lines := make([][]string, len(commits))
|
||||
|
||||
var displayFunc func(*commands.Commit) []string
|
||||
var displayFunc func(*commands.Commit, map[string]bool) []string
|
||||
if fullDescription {
|
||||
displayFunc = getFullDescriptionDisplayStringsForCommit
|
||||
} else {
|
||||
@@ -20,13 +20,13 @@ func GetCommitListDisplayStrings(commits []*commands.Commit, fullDescription boo
|
||||
}
|
||||
|
||||
for i := range commits {
|
||||
lines[i] = displayFunc(commits[i])
|
||||
lines[i] = displayFunc(commits[i], cherryPickedCommitShaMap)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
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)
|
||||
@@ -58,7 +58,7 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
if cherryPickedCommitShaMap[c.Sha] {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
@@ -74,10 +74,10 @@ func getFullDescriptionDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
|
||||
truncatedAuthor := utils.TruncateWithEllipsis(c.Author, 17)
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)}
|
||||
return []string{shaColor.Sprint(c.ShortSha()), secondColumnString, yellow.Sprint(truncatedAuthor), tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
func getDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
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)
|
||||
@@ -109,7 +109,7 @@ func getDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
if cherryPickedCommitShaMap[c.Sha] {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
@@ -122,5 +122,5 @@ func getDisplayStringsForCommit(c *commands.Commit) []string {
|
||||
tagString = utils.ColoredStringDirect(strings.Join(c.Tags, " "), tagColor) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha[:8]), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
return []string{shaColor.Sprint(c.ShortSha()), actionString + tagString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -17,11 +17,11 @@ func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error
|
||||
menuItems := make([]*menuItem, len(options))
|
||||
for i, option := range options {
|
||||
// note to self. Never, EVER, close over loop variables in a function
|
||||
innerOption := option
|
||||
option := option
|
||||
menuItems[i] = &menuItem{
|
||||
displayString: innerOption,
|
||||
displayString: option,
|
||||
onPress: func() error {
|
||||
return gui.genericMergeCommand(innerOption)
|
||||
return gui.genericMergeCommand(option)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -34,14 +34,12 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if commit == nil {
|
||||
return gui.newStringTask("main", "No reflog history")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.ReflogCommits.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -49,12 +47,17 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) 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 {
|
||||
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" {
|
||||
return gui.renderReflogCommitsWithSelection()
|
||||
@@ -67,7 +70,7 @@ func (gui *Gui) renderReflogCommitsWithSelection() error {
|
||||
commitsView := gui.getCommitsView()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.ReflogCommits.SelectedLine, len(gui.State.ReflogCommits))
|
||||
displayStrings := presentation.GetCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
displayStrings := presentation.GetReflogCommitListDisplayStrings(gui.State.ReflogCommits, gui.State.ScreenMode != SCREEN_NORMAL)
|
||||
gui.renderDisplayStrings(commitsView, displayStrings)
|
||||
if gui.g.CurrentView() == commitsView && commitsView.Context == "reflog-commits" {
|
||||
if err := gui.handleReflogCommitSelect(gui.g, commitsView); err != nil {
|
||||
@@ -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 {
|
||||
return gui.handleCheckoutRef(commit.Sha)
|
||||
return gui.handleCheckoutRef(commit.Sha, handleCheckoutRefOptions{})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,10 +38,7 @@ func (gui *Gui) handleRemoteBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.newStringTask("main", "No branches for this remote")
|
||||
}
|
||||
|
||||
gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||
if err := gui.focusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine, len(gui.State.RemoteBranches), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.RemoteBranches.SelectedLine)
|
||||
|
||||
branchName := fmt.Sprintf("%s/%s", remote.Name, remoteBranch.Name)
|
||||
|
||||
@@ -79,7 +76,7 @@ func (gui *Gui) handleCheckoutRemoteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
if remoteBranch == 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 gui.switchBranchesPanelContext("local-branches")
|
||||
|
||||
@@ -39,9 +39,7 @@ func (gui *Gui) handleRemoteSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if remote == nil {
|
||||
return gui.newStringTask("main", "No remotes")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Remotes.SelectedLine, len(gui.State.Remotes), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Remotes.SelectedLine)
|
||||
|
||||
return gui.newStringTask("main", fmt.Sprintf("%s\nUrls:\n%s", utils.ColoredString(remote.Name, color.FgGreen), strings.Join(remote.Urls, "\n")))
|
||||
}
|
||||
|
||||
+33
-20
@@ -4,13 +4,44 @@ import (
|
||||
"fmt"
|
||||
|
||||
"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 {
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
menuItems := make([]*menuItem, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
innerStrength := strength
|
||||
strength := strength
|
||||
menuItems[i] = &menuItem{
|
||||
displayStrings: []string{
|
||||
fmt.Sprintf("%s reset", strength),
|
||||
@@ -19,25 +50,7 @@ func (gui *Gui) createResetMenu(ref string) error {
|
||||
),
|
||||
},
|
||||
onPress: func() error {
|
||||
if err := gui.GitCommand.ResetToCommit(ref, innerStrength); err != nil {
|
||||
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())
|
||||
return gui.resetToRef(ref, strength, commands.RunCommandOptions{})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,19 @@ func (gui *Gui) handleOpenSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.isSearching = true
|
||||
gui.State.Searching.view = v
|
||||
gui.renderString(gui.g, "search", "")
|
||||
gui.switchFocus(gui.g, v, gui.getSearchView())
|
||||
if err := gui.switchFocus(gui.g, v, gui.getSearchView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSearch(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Searching.searchString = gui.getSearchView().Buffer()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -85,7 +90,5 @@ func (gui *Gui) handleSearchEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.onSearchEscape()
|
||||
|
||||
return nil
|
||||
return gui.onSearchEscape()
|
||||
}
|
||||
|
||||
+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())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelectionWithPrompt(false)
|
||||
func (gui *Gui) handleToggleStagedSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
return gui.applySelection(state.SecondaryFocused)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if !reverse && state.SecondaryFocused {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||
} else if reverse && !state.SecondaryFocused && !gui.Config.GetUserConfig().GetBool("gui.skipUnstageLineWarning") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getMainView(), false, "unstage lines", "Are you sure you want to unstage these lines? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.applySelection(reverse)
|
||||
}, nil)
|
||||
if state.SecondaryFocused {
|
||||
// for backwards compatibility
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -158,11 +159,3 @@ func (gui *Gui) applySelection(reverse bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDownSecondaryWhileStaging(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
state.SecondaryFocused = !state.SecondaryFocused
|
||||
|
||||
return gui.refreshStagingPanel(false, -1)
|
||||
}
|
||||
|
||||
+10
-8
@@ -34,14 +34,12 @@ func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if stashEntry == nil {
|
||||
return gui.newStringTask("main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Stash.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -97,9 +95,11 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
|
||||
return gui.createErrorPanel(g, errorMessage)
|
||||
}
|
||||
if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -109,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 {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
+10
-1
@@ -22,11 +22,20 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
// contents end up cleared
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v.Clear()
|
||||
// TODO: base this off of the current branch
|
||||
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
if err := gui.updateWorkTreeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
|
||||
if gui.State.WorkingTreeState != "normal" {
|
||||
|
||||
@@ -34,9 +34,7 @@ func (gui *Gui) handleTagSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if tag == nil {
|
||||
return gui.newStringTask("main", "No tags")
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Tags.SelectedLine, len(gui.State.Tags), v); err != nil {
|
||||
return err
|
||||
}
|
||||
v.FocusPoint(0, gui.State.Panels.Tags.SelectedLine)
|
||||
|
||||
cmd := gui.OSCommand.ExecutableFromString(
|
||||
gui.GitCommand.GetBranchGraphCmdStr(tag.Name),
|
||||
@@ -57,7 +55,7 @@ func (gui *Gui) refreshTags() error {
|
||||
gui.State.Tags = tags
|
||||
|
||||
if gui.getBranchesView().Context == "tags" {
|
||||
gui.renderTagsWithSelection()
|
||||
return gui.renderTagsWithSelection()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -83,7 +81,7 @@ func (gui *Gui) handleCheckoutTag(g *gocui.Gui, v *gocui.View) error {
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
if err := gui.handleCheckoutRef(tag.Name); err != nil {
|
||||
if err := gui.handleCheckoutRef(tag.Name, handleCheckoutRefOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchBranchesPanelContext("local-branches")
|
||||
|
||||
@@ -18,7 +18,17 @@ func (gui *Gui) newCmdTask(viewName string, cmd *exec.Cmd) error {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -49,7 +59,8 @@ func (gui *Gui) newStringTask(viewName string, str string) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
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 {
|
||||
@@ -69,7 +80,9 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
view.Clear()
|
||||
},
|
||||
func() {
|
||||
gui.g.Update(func(*gocui.Gui) error { return nil })
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return nil
|
||||
})
|
||||
})
|
||||
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 {
|
||||
gui.State.Updating = false
|
||||
gui.statusManager.removeStatus("updating")
|
||||
if err := gui.renderString(gui.g, "appStatus", ""); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.renderString(gui.g, "appStatus", "")
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, "Update failed: "+err.Error())
|
||||
}
|
||||
|
||||
+6
-33
@@ -218,25 +218,18 @@ func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
return v.SetOrigin(0, 0)
|
||||
}
|
||||
|
||||
// if the cursor down past the last item, move it to the last line
|
||||
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
||||
v.FocusPoint(cx, cy)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) cleanString(s string) string {
|
||||
output := string(bom.Clean([]byte(s)))
|
||||
return utils.NormalizeLinefeeds(output)
|
||||
}
|
||||
|
||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
|
||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) {
|
||||
v.Clear()
|
||||
fmt.Fprint(v, gui.cleanString(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderString resets the origin of a view and sets its content
|
||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v, err := g.View(viewName)
|
||||
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 {
|
||||
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 {
|
||||
@@ -263,7 +256,8 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -349,22 +343,6 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
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) {
|
||||
// TODO: find out why we're doing this
|
||||
if *line == -1 {
|
||||
@@ -409,11 +387,6 @@ func (gui *Gui) renderPanelOptions() error {
|
||||
return gui.renderGlobalOptions()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := gui.g.SetCurrentView(v.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) isPopupPanel(viewName string) bool {
|
||||
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
|
||||
}
|
||||
|
||||
+114
-3
@@ -360,6 +360,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "undo",
|
||||
Other: "undo",
|
||||
}, &i18n.Message{
|
||||
ID: "undoReflog",
|
||||
Other: "undo (via reflog) (experimental)",
|
||||
}, &i18n.Message{
|
||||
ID: "redoReflog",
|
||||
Other: "redo (via reflog) (experimental)",
|
||||
}, &i18n.Message{
|
||||
ID: "pop",
|
||||
Other: "pop",
|
||||
@@ -501,16 +507,19 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: `select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageSelection",
|
||||
Other: `stage selection`,
|
||||
Other: `toggle line staged / unstaged`,
|
||||
}, &i18n.Message{
|
||||
ID: "ResetSelection",
|
||||
Other: `reset selection`,
|
||||
Other: `delete change (git reset)`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleDragSelect",
|
||||
Other: `toggle drag select`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectHunk",
|
||||
Other: `toggle select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectionForPatch",
|
||||
Other: `add/remove line(s) to patch`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "TogglePanel",
|
||||
@@ -564,6 +573,54 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Options",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessageTitle",
|
||||
Other: "Commit Message",
|
||||
}, &i18n.Message{
|
||||
ID: "Local-BranchesTitle",
|
||||
Other: "Branches Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "SearchTitle",
|
||||
Other: "Search",
|
||||
}, &i18n.Message{
|
||||
ID: "TagsTitle",
|
||||
Other: "Tags Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Branch-CommitsTitle",
|
||||
Other: "Commits Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "MenuTitle",
|
||||
Other: "Menu",
|
||||
}, &i18n.Message{
|
||||
ID: "RemotesTitle",
|
||||
Other: "Remotes Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsTitle",
|
||||
Other: "Credentials",
|
||||
}, &i18n.Message{
|
||||
ID: "Remote-BranchesTitle",
|
||||
Other: "Remote Branches (in Remotes tab)",
|
||||
}, &i18n.Message{
|
||||
ID: "Patch-BuildingTitle",
|
||||
Other: "Patch Building",
|
||||
}, &i18n.Message{
|
||||
ID: "InformationTitle",
|
||||
Other: "Information",
|
||||
}, &i18n.Message{
|
||||
ID: "SecondaryTitle",
|
||||
Other: "Secondary",
|
||||
}, &i18n.Message{
|
||||
ID: "Reflog-CommitsTitle",
|
||||
Other: "Reflog Tab",
|
||||
}, &i18n.Message{
|
||||
ID: "Title",
|
||||
Other: "Title",
|
||||
}, &i18n.Message{
|
||||
ID: "GlobalTitle",
|
||||
Other: "Global Keybindings",
|
||||
}, &i18n.Message{
|
||||
ID: "MerginTitle",
|
||||
Other: "Mergin",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "all merge conflicts resolved. Continue?",
|
||||
@@ -651,6 +708,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll up",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollUpMainPanel",
|
||||
Other: "scroll up main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "scrollDownMainPanel",
|
||||
Other: "scroll down main panel",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Amend Commit",
|
||||
@@ -684,6 +747,15 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPickingStatus",
|
||||
Other: "cherry-picking",
|
||||
}, &i18n.Message{
|
||||
ID: "UndoingStatus",
|
||||
Other: "undoing",
|
||||
}, &i18n.Message{
|
||||
ID: "RedoingStatus",
|
||||
Other: "redoing",
|
||||
}, &i18n.Message{
|
||||
ID: "CheckingOutStatus",
|
||||
Other: "checking out",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFiles",
|
||||
Other: "Commit files",
|
||||
@@ -692,7 +764,7 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "view commit's files",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit files",
|
||||
Other: "Commit Files",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "go back",
|
||||
@@ -951,6 +1023,45 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &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"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,6 @@ type Task struct {
|
||||
|
||||
type ViewBufferManager struct {
|
||||
writer io.Writer
|
||||
waitingTask *Task
|
||||
currentTask *Task
|
||||
waitingMutex 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 {
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Stderr = cmd.Stdout
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
if cmd.ProcessState == nil {
|
||||
if err := kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
if err := commands.Kill(cmd); err != nil {
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
}()
|
||||
|
||||
loadingMutex := sync.Mutex{}
|
||||
|
||||
// not sure if it's the right move to redefine this or not
|
||||
m.readLines = make(chan int, 1024)
|
||||
|
||||
@@ -82,11 +75,13 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
defer ticker.Stop()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
m.writer.Write([]byte("loading..."))
|
||||
_, _ = m.writer.Write([]byte("loading..."))
|
||||
m.refreshView()
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
@@ -98,13 +93,16 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
case linesToRead := <-m.readLines:
|
||||
for i := 0; i < linesToRead; i++ {
|
||||
ok := scanner.Scan()
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
loaded = true
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
@@ -112,10 +110,11 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
_, _ = m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
}
|
||||
m.refreshView()
|
||||
case <-stop:
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
}
|
||||
@@ -124,6 +123,12 @@ func (m *ViewBufferManager) NewCmdTask(cmd *exec.Cmd, linesToRead int) func(chan
|
||||
m.Log.Warn(err)
|
||||
}
|
||||
|
||||
m.refreshView()
|
||||
|
||||
if onDone != nil {
|
||||
onDone()
|
||||
}
|
||||
|
||||
close(done)
|
||||
}()
|
||||
|
||||
@@ -141,7 +146,7 @@ func (t *ViewBufferManager) Close() {
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan struct{}, 1)
|
||||
c := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
t.currentTask.Stop()
|
||||
@@ -170,7 +175,10 @@ func (m *ViewBufferManager) NewTask(f func(stop chan struct{}) error) error {
|
||||
|
||||
m.waitingMutex.Lock()
|
||||
defer m.waitingMutex.Unlock()
|
||||
|
||||
m.Log.Infof("done waiting")
|
||||
if taskID < m.newTaskId {
|
||||
m.Log.Infof("returning cos the task is obsolete")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -214,15 +222,4 @@ func (t *Task) Stop() {
|
||||
<-t.notifyStopped
|
||||
t.Log.Info("received notifystopped message")
|
||||
t.stopped = true
|
||||
return
|
||||
}
|
||||
|
||||
// kill kills a process
|
||||
func kill(cmd *exec.Cmd) error {
|
||||
if cmd.Process == nil {
|
||||
// somebody got to it before we were able to, poor bastard
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
|
||||
@@ -316,3 +316,9 @@ func TruncateWithEllipsis(str string, limit int) string {
|
||||
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,8 +196,6 @@ func TestDisplayArraysAligned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type myStruct struct{}
|
||||
|
||||
// TestGetPaddedDisplayStrings is a function.
|
||||
func TestGetPaddedDisplayStrings(t *testing.T) {
|
||||
type scenario struct {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
@@ -60,33 +61,85 @@ func formatTitle(title string) string {
|
||||
|
||||
func formatBinding(binding *gui.Binding) string {
|
||||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%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 {
|
||||
bindingSections := []*bindingSection{}
|
||||
|
||||
// TODO: add context-based keybindings
|
||||
for _, binding := range mApp.Gui.GetInitialKeybindings() {
|
||||
if binding.Description == "" {
|
||||
continue
|
||||
bindings := mApp.Gui.GetInitialKeybindings()
|
||||
|
||||
type contextAndViewType struct {
|
||||
context string
|
||||
viewName string
|
||||
}
|
||||
|
||||
contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{}
|
||||
|
||||
for _, binding := range bindings {
|
||||
contexts := []string{}
|
||||
if len(binding.Contexts) == 0 {
|
||||
contexts = append(contexts, "")
|
||||
} else {
|
||||
contexts = append(contexts, binding.Contexts...)
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
viewName = "global"
|
||||
}
|
||||
title := localisedTitle(mApp, viewName)
|
||||
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
}
|
||||
|
||||
for contextName, contextBindings := range mApp.Gui.GetContextMap() {
|
||||
translatedView := localisedTitle(mApp, contextBindings[0].ViewName)
|
||||
translatedContextName := localisedTitle(mApp, contextName)
|
||||
title := fmt.Sprintf("%s (%s)", translatedView, translatedContextName)
|
||||
translatedView := localisedTitle(mApp, viewName)
|
||||
var title string
|
||||
if contextAndView.context == "" {
|
||||
addendum := " " + mApp.Tr.SLocalize("Panel")
|
||||
if viewName == "global" {
|
||||
addendum = ""
|
||||
}
|
||||
title = fmt.Sprintf("%s%s", translatedView, addendum)
|
||||
} else {
|
||||
translatedContextName := localisedTitle(mApp, contextAndView.context)
|
||||
title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.SLocalize("Panel"), translatedContextName)
|
||||
}
|
||||
|
||||
for _, binding := range contextBindings {
|
||||
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 {
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu"))
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("Keybindings"))
|
||||
|
||||
for _, section := range bindingSections {
|
||||
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
|
||||
|
||||
go get github.com/kr/pty
|
||||
go get github.com/creack/pty
|
||||
|
||||
## Example
|
||||
|
||||
@@ -14,7 +14,7 @@ Pty is a Go package for using unix pseudo-terminals.
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kr/pty"
|
||||
"github.com/creack/pty"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -50,7 +50,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/kr/pty"
|
||||
"github.com/creack/pty"
|
||||
"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
|
||||
|
||||
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 {
|
||||
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)))
|
||||
}
|
||||
Generated
Vendored
+1
-1
@@ -11,7 +11,7 @@ func open() (pty, tty *os.File, err error) {
|
||||
* from ptm(4):
|
||||
* The PTMGET command allocates a free pseudo terminal, changes its
|
||||
* 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.
|
||||
*/
|
||||
|
||||
+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
|
||||
|
||||
+4
-1
@@ -39,12 +39,15 @@ func StartWithSize(c *exec.Cmd, sz *Winsize) (pty *os.File, err error) {
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = tty
|
||||
}
|
||||
c.Stdin = tty
|
||||
if c.Stdin == nil {
|
||||
c.Stdin = tty
|
||||
}
|
||||
if c.SysProcAttr == nil {
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
c.SysProcAttr.Setctty = true
|
||||
c.SysProcAttr.Setsid = true
|
||||
c.SysProcAttr.Ctty = int(tty.Fd())
|
||||
err = c.Start()
|
||||
if err != nil {
|
||||
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 .
|
||||
Generated
Vendored
+7
-7
@@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
// +build !windows,!solaris
|
||||
|
||||
package pty
|
||||
|
||||
@@ -8,15 +8,15 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// InheritSize applies the terminal size of master to slave. This should be run
|
||||
// in a signal handler for syscall.SIGWINCH to automatically resize the slave when
|
||||
// the master receives a window size change notification.
|
||||
func InheritSize(master, slave *os.File) error {
|
||||
size, err := GetsizeFull(master)
|
||||
// InheritSize applies the terminal size of pty to tty. This should be run
|
||||
// in a signal handler for syscall.SIGWINCH to automatically resize the tty when
|
||||
// the pty receives a window size change notification.
|
||||
func InheritSize(pty, tty *os.File) error {
|
||||
size, err := GetsizeFull(pty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = Setsize(slave, size)
|
||||
err = Setsize(tty, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
//
|
||||
|
||||
package pty
|
||||
|
||||
import (
|
||||
"os"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
TIOCGWINSZ = 21608 // 'T' << 8 | 104
|
||||
TIOCSWINSZ = 21607 // 'T' << 8 | 103
|
||||
)
|
||||
|
||||
// Winsize describes the terminal size.
|
||||
type Winsize struct {
|
||||
Rows uint16 // ws_row: Number of rows (in cells)
|
||||
Cols uint16 // ws_col: Number of columns (in cells)
|
||||
X uint16 // ws_xpixel: Width in pixels
|
||||
Y uint16 // ws_ypixel: Height in pixels
|
||||
}
|
||||
|
||||
// GetsizeFull returns the full terminal size description.
|
||||
func GetsizeFull(t *os.File) (size *Winsize, err error) {
|
||||
var wsz *unix.Winsize
|
||||
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Winsize{wsz.Row, wsz.Col, wsz.Xpixel, wsz.Ypixel}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Get Windows Size
|
||||
func Getsize(t *os.File) (rows, cols int, err error) {
|
||||
var wsz *unix.Winsize
|
||||
wsz, err = unix.IoctlGetWinsize(int(t.Fd()), TIOCGWINSZ)
|
||||
|
||||
if err != nil {
|
||||
return 80, 25, err
|
||||
} else {
|
||||
return int(wsz.Row), int(wsz.Col), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Setsize resizes t to s.
|
||||
func Setsize(t *os.File, ws *Winsize) error {
|
||||
wsz := unix.Winsize{ws.Rows, ws.Cols, ws.X, ws.Y}
|
||||
return unix.IoctlSetWinsize(int(t.Fd()), TIOCSWINSZ, &wsz)
|
||||
}
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
|
||||
// cgo -godefs types_freebsd.go
|
||||
|
||||
package pty
|
||||
|
||||
const (
|
||||
_C_SPECNAMELEN = 0xff
|
||||
)
|
||||
|
||||
type fiodgnameArg struct {
|
||||
Len int32
|
||||
Buf *byte
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user