Compare commits
91 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 |
@@ -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: /.*/
|
||||
32
.github/workflows/cd.yml
vendored
Normal file
32
.github/workflows/cd.yml
vendored
Normal file
@@ -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"
|
||||
36
.github/workflows/ci.yml
vendored
Normal file
36
.github/workflows/ci.yml
vendored
Normal file
@@ -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,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
|
||||
|
||||
90
README.md
90
README.md
@@ -1,28 +1,50 @@
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []() [](https://www.tickgit.com/browse?repo=github.com/jesseduffield/lazygit)
|
||||
# lazygit
|
||||
|
||||
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.
|
||||
@@ -67,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:
|
||||
@@ -109,10 +132,6 @@ Released versions are available for different platforms, see <https://anaconda.o
|
||||
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
|
||||
@@ -125,18 +144,24 @@ 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).
|
||||
### Keybindings
|
||||
|
||||
## Changing Directory On Exit
|
||||
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):
|
||||
|
||||
@@ -156,14 +181,25 @@ lg()
|
||||
|
||||
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 the [configuration docs](docs/Config.md).
|
||||
Check out the [configuration docs](docs/Config.md).
|
||||
|
||||
## Custom Pagers
|
||||
### 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
|
||||
@@ -179,14 +215,14 @@ See the [docs](docs/Custom_Pagers.md)
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ Default path for the config file:
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
@@ -128,6 +130,7 @@ Default path for the config file:
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
resetCherryPick: '<c-R>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
@@ -137,7 +140,6 @@ Default path for the config file:
|
||||
toggleDragSelect-alt: 'V'
|
||||
toggleSelectHunk: 'a'
|
||||
pickBothHunks: 'b'
|
||||
undo: 'z'
|
||||
```
|
||||
|
||||
## Platform Defaults
|
||||
|
||||
24
docs/Undoing.md
Normal file
24
docs/Undoing.md
Normal file
@@ -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.
|
||||
@@ -10,11 +10,21 @@
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: refresh
|
||||
<kbd>x</kbd>: open menu
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: prev screen mode
|
||||
<kbd>:</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches Panel
|
||||
|
||||
<pre>
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
@@ -29,6 +39,8 @@
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
@@ -41,6 +53,7 @@
|
||||
<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)
|
||||
@@ -50,6 +63,7 @@
|
||||
<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)
|
||||
@@ -60,6 +74,7 @@
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit Files Panel
|
||||
@@ -71,6 +86,15 @@
|
||||
<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)
|
||||
@@ -97,6 +121,7 @@
|
||||
<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>
|
||||
|
||||
## Commits Panel (Reflog Tab)
|
||||
@@ -126,6 +151,7 @@
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main Panel (Merging)
|
||||
@@ -156,8 +182,7 @@
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -167,8 +192,8 @@
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -184,12 +209,21 @@
|
||||
<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
|
||||
|
||||
@@ -10,11 +10,21 @@
|
||||
<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>
|
||||
|
||||
## Branches Panel
|
||||
|
||||
<pre>
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
@@ -29,6 +39,8 @@
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Branches Panel (Remote Branches (in Remotes tab))
|
||||
@@ -41,6 +53,7 @@
|
||||
<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)
|
||||
@@ -50,6 +63,7 @@
|
||||
<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)
|
||||
@@ -60,6 +74,7 @@
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit bestanden Panel
|
||||
@@ -71,6 +86,15 @@
|
||||
<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)
|
||||
@@ -97,6 +121,7 @@
|
||||
<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>
|
||||
|
||||
## Commits Panel (Reflog Tab)
|
||||
@@ -126,6 +151,7 @@
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Hoofd Panel (Merging)
|
||||
@@ -156,8 +182,7 @@
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -167,8 +192,8 @@
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -184,12 +209,21 @@
|
||||
<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
|
||||
|
||||
@@ -10,11 +10,21 @@
|
||||
<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>
|
||||
|
||||
## Gałęzie Panel
|
||||
|
||||
<pre>
|
||||
<kbd>]</kbd>: next tab
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Branches Tab)
|
||||
|
||||
<pre>
|
||||
@@ -29,6 +39,8 @@
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Gałęzie Panel (Remote Branches (in Remotes tab))
|
||||
@@ -41,6 +53,7 @@
|
||||
<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)
|
||||
@@ -50,6 +63,7 @@
|
||||
<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)
|
||||
@@ -60,6 +74,7 @@
|
||||
<kbd>P</kbd>: push tag
|
||||
<kbd>n</kbd>: create tag
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Commit files Panel
|
||||
@@ -71,6 +86,15 @@
|
||||
<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)
|
||||
@@ -97,6 +121,7 @@
|
||||
<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>
|
||||
|
||||
## Commity Panel (Reflog Tab)
|
||||
@@ -126,6 +151,7 @@
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>/</kbd>: start search
|
||||
</pre>
|
||||
|
||||
## Main Panel (Merging)
|
||||
@@ -138,7 +164,7 @@
|
||||
<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)
|
||||
@@ -156,8 +182,7 @@
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -167,8 +192,8 @@
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>space</kbd>: stage selection
|
||||
<kbd>d</kbd>: reset selection
|
||||
<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
|
||||
@@ -184,12 +209,21 @@
|
||||
<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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 178 KiB |
BIN
docs/resources/rebase.gif
Normal file
BIN
docs/resources/rebase.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/resources/staging.gif
Normal file
BIN
docs/resources/staging.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 650 KiB |
BIN
docs/resources/undo2.gif
Normal file
BIN
docs/resources/undo2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 539 KiB |
6
go.mod
6
go.mod
@@ -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,8 +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.20200301081700-d6e485450113
|
||||
github.com/jesseduffield/pty v1.2.1
|
||||
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
|
||||
|
||||
10
go.sum
10
go.sum
@@ -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=
|
||||
@@ -76,12 +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.20200224201655-5024a02682ed h1:glGs+mzPZOl1iHiUsBW3918WeFwqsbQQ/jtLkkQXDi4=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200224201655-5024a02682ed/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113 h1:jHZRVJUWsU8HaQ0crocz0i0BkpOqFLDJEO/AtBp+Ecs=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20200301081700-d6e485450113/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/pty v1.2.1 h1:7xYBiwNH0PpWqC8JmvrPq1a/ksNqyCavzWu9pbBGYWI=
|
||||
github.com/jesseduffield/pty v1.2.1/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
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=
|
||||
|
||||
6
main.go
6
main.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -322,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
|
||||
@@ -522,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`
|
||||
@@ -563,7 +578,7 @@ func (c *GitCommand) ShowCmdStr(sha string) string {
|
||||
}
|
||||
|
||||
func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string {
|
||||
return fmt.Sprintf("git log --graph --color=always --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
|
||||
@@ -866,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
|
||||
}
|
||||
@@ -1105,26 +1120,42 @@ 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 {
|
||||
return nil, err
|
||||
// assume error means we have no reflog
|
||||
return []*Commit{}, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
commits := make([]*Commit, len(lines))
|
||||
re := regexp.MustCompile(`(\w+).*HEAD@\{\d+\}: (.*)`)
|
||||
for i, line := range lines {
|
||||
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 {
|
||||
continue
|
||||
return false, nil
|
||||
}
|
||||
|
||||
commits[i] = &Commit{
|
||||
commit := &Commit{
|
||||
Sha: match[1],
|
||||
Name: match[2],
|
||||
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
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
@@ -1163,3 +1194,7 @@ func (c *GitCommand) GetPager(width int) string {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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=always", "--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")
|
||||
}
|
||||
@@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
@@ -409,3 +426,31 @@ func Kill(cmd *exec.Cmd) error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -314,6 +314,8 @@ keybinding:
|
||||
prevTab: '['
|
||||
nextScreenMode: '+'
|
||||
prevScreenMode: '_'
|
||||
undo: 'z'
|
||||
redo: '<c-z>'
|
||||
status:
|
||||
checkForUpdate: 'u'
|
||||
recentRepos: '<enter>'
|
||||
@@ -334,6 +336,7 @@ keybinding:
|
||||
checkoutBranchByName: 'c'
|
||||
forceCheckoutBranch: 'F'
|
||||
rebaseBranch: 'r'
|
||||
renameBranch: 'R'
|
||||
mergeIntoCurrentBranch: 'M'
|
||||
viewGitFlowOptions: 'i'
|
||||
fastForward: 'f'
|
||||
@@ -359,6 +362,7 @@ keybinding:
|
||||
tagCommit: 'T'
|
||||
toggleDiffCommit: 'i'
|
||||
checkoutCommit: '<space>'
|
||||
resetCherryPick: '<c-R>'
|
||||
stash:
|
||||
popStash: 'g'
|
||||
commitFiles:
|
||||
@@ -368,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)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -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,16 +36,15 @@ 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),
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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{
|
||||
|
||||
@@ -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")
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
@@ -214,6 +213,7 @@ type guiState struct {
|
||||
Ptmx *os.File
|
||||
PrevMainWidth int
|
||||
PrevMainHeight int
|
||||
OldInformation string
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
@@ -221,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),
|
||||
@@ -278,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
|
||||
}
|
||||
@@ -288,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
|
||||
}
|
||||
@@ -335,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)
|
||||
}
|
||||
@@ -381,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")
|
||||
@@ -491,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
|
||||
@@ -518,7 +539,7 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
g.DeleteView("limit")
|
||||
_ = g.DeleteView("limit")
|
||||
|
||||
sidePanelWidthRatio := gui.Config.GetUserConfig().GetFloat64("gui.sidePanelWidth")
|
||||
|
||||
@@ -538,7 +559,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
panelSplitX := width - 1
|
||||
mainPanelLeft := leftSideWidth + 1
|
||||
mainPanelRight := width - 1
|
||||
secondaryPanelLeft := width - 1
|
||||
@@ -547,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 {
|
||||
@@ -558,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
|
||||
}
|
||||
@@ -693,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
|
||||
@@ -758,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 {
|
||||
@@ -812,9 +835,7 @@ 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 {
|
||||
return err
|
||||
}
|
||||
listView.view.FocusPoint(0, listView.selectedLine)
|
||||
}
|
||||
|
||||
mainViewWidth, mainViewHeight := gui.getMainView().Size()
|
||||
@@ -909,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
|
||||
}
|
||||
|
||||
@@ -918,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"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -295,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: "",
|
||||
@@ -312,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"),
|
||||
@@ -568,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"},
|
||||
@@ -609,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",
|
||||
@@ -645,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",
|
||||
@@ -830,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"},
|
||||
@@ -892,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",
|
||||
@@ -1318,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",
|
||||
@@ -1424,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"} {
|
||||
@@ -1454,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
|
||||
@@ -241,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
|
||||
|
||||
@@ -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"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,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)
|
||||
@@ -57,7 +58,8 @@ func (gui *Gui) handleToggleSelectionForPatch(g *gocui.Gui, v *gocui.View) error
|
||||
// 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
|
||||
}
|
||||
|
||||
toggleFunc(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
@@ -92,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)}
|
||||
}
|
||||
|
||||
37
pkg/gui/presentation/reflog_commits.go
Normal file
37
pkg/gui/presentation/reflog_commits.go
Normal file
@@ -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)}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ package gui
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/jesseduffield/pty"
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
func (gui *Gui) onResize() error {
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,7 @@ 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),
|
||||
@@ -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")))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -159,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)
|
||||
}
|
||||
|
||||
@@ -34,9 +34,7 @@ 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),
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -59,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 {
|
||||
@@ -80,7 +81,6 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
},
|
||||
func() {
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
gui.Log.Warn("updating view")
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
202
pkg/gui/undoing.go
Normal file
202
pkg/gui/undoing.go
Normal file
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -741,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",
|
||||
@@ -1008,12 +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",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ type Task struct {
|
||||
|
||||
type ViewBufferManager struct {
|
||||
writer io.Writer
|
||||
waitingTask *Task
|
||||
currentTask *Task
|
||||
waitingMutex sync.Mutex
|
||||
taskIDMutex sync.Mutex
|
||||
@@ -79,7 +78,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, linesToRead i
|
||||
loadingMutex.Lock()
|
||||
if !loaded {
|
||||
m.beforeStart()
|
||||
m.writer.Write([]byte("loading..."))
|
||||
_, _ = m.writer.Write([]byte("loading..."))
|
||||
m.refreshView()
|
||||
}
|
||||
loadingMutex.Unlock()
|
||||
@@ -111,7 +110,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, linesToRead i
|
||||
m.refreshView()
|
||||
break outer
|
||||
}
|
||||
m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
_, _ = m.writer.Write(append(scanner.Bytes(), []byte("\n")...))
|
||||
}
|
||||
m.refreshView()
|
||||
case <-stop:
|
||||
@@ -223,5 +222,4 @@ func (t *Task) Stop() {
|
||||
<-t.notifyStopped
|
||||
t.Log.Info("received notifystopped message")
|
||||
t.stopped = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -83,9 +83,7 @@ func getBindingSections(mApp *app.App) []*bindingSection {
|
||||
if len(binding.Contexts) == 0 {
|
||||
contexts = append(contexts, "")
|
||||
} else {
|
||||
for _, context := range binding.Contexts {
|
||||
contexts = append(contexts, context)
|
||||
}
|
||||
contexts = append(contexts, binding.Contexts...)
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
|
||||
14
vendor/github.com/creack/pty/Dockerfile.riscv
generated
vendored
Normal file
14
vendor/github.com/creack/pty/Dockerfile.riscv
generated
vendored
Normal file
@@ -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
|
||||
0
vendor/github.com/jesseduffield/pty/License → vendor/github.com/creack/pty/LICENSE
generated
vendored
0
vendor/github.com/jesseduffield/pty/License → vendor/github.com/creack/pty/LICENSE
generated
vendored
@@ -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"
|
||||
)
|
||||
|
||||
0
vendor/github.com/jesseduffield/pty/doc.go → vendor/github.com/creack/pty/doc.go
generated
vendored
0
vendor/github.com/jesseduffield/pty/doc.go → vendor/github.com/creack/pty/doc.go
generated
vendored
4
vendor/github.com/creack/pty/go.mod
generated
vendored
Normal file
4
vendor/github.com/creack/pty/go.mod
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
module github.com/creack/pty
|
||||
|
||||
go 1.13
|
||||
|
||||
2
vendor/github.com/jesseduffield/pty/ioctl.go → vendor/github.com/creack/pty/ioctl.go
generated
vendored
2
vendor/github.com/jesseduffield/pty/ioctl.go → vendor/github.com/creack/pty/ioctl.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
// +build !windows,!solaris
|
||||
|
||||
package pty
|
||||
|
||||
30
vendor/github.com/creack/pty/ioctl_solaris.go
generated
vendored
Normal file
30
vendor/github.com/creack/pty/ioctl_solaris.go
generated
vendored
Normal file
@@ -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))
|
||||
}
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -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
vendor/github.com/creack/pty/pty_solaris.go
generated
vendored
Normal file
139
vendor/github.com/creack/pty/pty_solaris.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd
|
||||
// +build !linux,!darwin,!freebsd,!dragonfly,!openbsd,!solaris
|
||||
|
||||
package pty
|
||||
|
||||
5
vendor/github.com/jesseduffield/pty/run.go → vendor/github.com/creack/pty/run.go
generated
vendored
5
vendor/github.com/jesseduffield/pty/run.go → vendor/github.com/creack/pty/run.go
generated
vendored
@@ -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
vendor/github.com/creack/pty/test_crosscompile.sh
generated
vendored
Normal file
50
vendor/github.com/creack/pty/test_crosscompile.sh
generated
vendored
Normal file
@@ -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 .
|
||||
14
vendor/github.com/jesseduffield/pty/util.go → vendor/github.com/creack/pty/util.go
generated
vendored
14
vendor/github.com/jesseduffield/pty/util.go → vendor/github.com/creack/pty/util.go
generated
vendored
@@ -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
vendor/github.com/creack/pty/util_solaris.go
generated
vendored
Normal file
51
vendor/github.com/creack/pty/util_solaris.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
13
vendor/github.com/creack/pty/ztypes_freebsd_arm64.go
generated
vendored
Normal file
13
vendor/github.com/creack/pty/ztypes_freebsd_arm64.go
generated
vendored
Normal file
@@ -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