mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 10:16:22 -04:00
Compare commits
143
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4465fec48 | ||
|
|
effda8291b | ||
|
|
7985e31020 | ||
|
|
866e0a618b | ||
|
|
a5ee61c117 | ||
|
|
08aad924d7 | ||
|
|
be02786dad | ||
|
|
949022db8c | ||
|
|
b1a090d4c0 | ||
|
|
39f3f150ed | ||
|
|
7e9f669421 | ||
|
|
6b769fb138 | ||
|
|
cc835a813e | ||
|
|
6103a4d13c | ||
|
|
69575dd4f3 | ||
|
|
bfcff3222c | ||
|
|
5adea789d0 | ||
|
|
78bbdca757 | ||
|
|
5cb82a49f8 | ||
|
|
9617737352 | ||
|
|
a251f6ad6c | ||
|
|
b61ca21a84 | ||
|
|
1ded318666 | ||
|
|
e15a99e626 | ||
|
|
d4eae73a68 | ||
|
|
5ba1eb2785 | ||
|
|
d71f5d951e | ||
|
|
adce8ad398 | ||
|
|
62ab41c310 | ||
|
|
85f7aa9d7b | ||
|
|
30ce7c8085 | ||
|
|
1827380c69 | ||
|
|
ea0baf58e6 | ||
|
|
1d96ade0ba | ||
|
|
82b3803164 | ||
|
|
f4ada537d2 | ||
|
|
956399a1ea | ||
|
|
057742d4af | ||
|
|
9c0a151dfa | ||
|
|
f9414f275d | ||
|
|
cc316ab6de | ||
|
|
8f00bfebce | ||
|
|
2dddd906f8 | ||
|
|
16ed3c2377 | ||
|
|
d44d164a5a | ||
|
|
90613056ce | ||
|
|
c05a1ae711 | ||
|
|
a0154dc525 | ||
|
|
6b9390409e | ||
|
|
8964cedf27 | ||
|
|
14ecc15e71 | ||
|
|
9e79ee5fe3 | ||
|
|
fdee0e1497 | ||
|
|
bf7726d130 | ||
|
|
6282d55919 | ||
|
|
e588355f57 | ||
|
|
c7a3b69eb9 | ||
|
|
b19943af01 | ||
|
|
015a04fac6 | ||
|
|
26ca41a40e | ||
|
|
6c4e7ee972 | ||
|
|
631cf1e873 | ||
|
|
585ea361f6 | ||
|
|
1a36cb9f3f | ||
|
|
5d982e1d70 | ||
|
|
4d734d594a | ||
|
|
b625eb5323 | ||
|
|
db7b472f9a | ||
|
|
8e46b8a275 | ||
|
|
cd989d8ebe | ||
|
|
261f30f49c | ||
|
|
7d7399a89f | ||
|
|
9df634f13f | ||
|
|
3ca1292fb4 | ||
|
|
13c1103815 | ||
|
|
b8bee4de51 | ||
|
|
d67b209e62 | ||
|
|
bf5871cc4f | ||
|
|
13326344f0 | ||
|
|
84870d4503 | ||
|
|
a9e2c8129f | ||
|
|
fd861826bc | ||
|
|
2be4359e87 | ||
|
|
c9a917b830 | ||
|
|
4df353d006 | ||
|
|
9ae7710850 | ||
|
|
87fe30d50d | ||
|
|
cff9850374 | ||
|
|
7cb54f4fee | ||
|
|
eb7e0e7252 | ||
|
|
074396a892 | ||
|
|
62cc036254 | ||
|
|
a8aeadfdb7 | ||
|
|
69df9ba105 | ||
|
|
5133113142 | ||
|
|
8051052ea7 | ||
|
|
ff8bc91a8e | ||
|
|
4e573e4bb1 | ||
|
|
2f6b7b9bc3 | ||
|
|
ee03a0be41 | ||
|
|
51a558040d | ||
|
|
6ecd691223 | ||
|
|
c4a2749a99 | ||
|
|
85f293af1a | ||
|
|
9cbd7fe69e | ||
|
|
4461dc68b7 | ||
|
|
6acabba417 | ||
|
|
7fb86d6e9c | ||
|
|
221433522d | ||
|
|
90084d115e | ||
|
|
a14794bf5c | ||
|
|
bf685cf832 | ||
|
|
8932d17393 | ||
|
|
de69fbd645 | ||
|
|
1998d0724f | ||
|
|
3928d0ebda | ||
|
|
d66ca7751c | ||
|
|
ba160cb5db | ||
|
|
3d76c734aa | ||
|
|
cddf056f4d | ||
|
|
41b00c8313 | ||
|
|
06892cb0ac | ||
|
|
77c5d1761d | ||
|
|
6ab5d7f69b | ||
|
|
c410d26006 | ||
|
|
db1fd85e12 | ||
|
|
1489a31e48 | ||
|
|
1f42c8a387 | ||
|
|
e229e26fbe | ||
|
|
5c55ce6555 | ||
|
|
1f801b91e4 | ||
|
|
0080684c7c | ||
|
|
c92e687d3b | ||
|
|
b6a31369da | ||
|
|
a694c458dd | ||
|
|
ced773ab18 | ||
|
|
a741b81b21 | ||
|
|
cbbb281011 | ||
|
|
e6fc332748 | ||
|
|
042ab2f99a | ||
|
|
a9ae5063c2 | ||
|
|
1932c2366b | ||
|
|
dd34adb36c |
@@ -0,0 +1,26 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
categories:
|
||||
- title: Features ✨
|
||||
labels:
|
||||
- feature
|
||||
- title: Enhancements 🔥
|
||||
labels:
|
||||
- enhancement
|
||||
- title: Fixes 🔧
|
||||
labels:
|
||||
- bug
|
||||
- title: Maintenance ⚙️
|
||||
labels:
|
||||
- maintenance
|
||||
- title: Docs 📖
|
||||
labels:
|
||||
- docs
|
||||
- title: I18n 🌎
|
||||
labels:
|
||||
- i18n
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Unshallow repo
|
||||
run: git fetch --prune --unshallow
|
||||
- name: Setup Go
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
@@ -47,13 +47,47 @@ jobs:
|
||||
run: |
|
||||
go test ./... -short
|
||||
integration-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
git-version:
|
||||
- 2.20.0 # oldest supported version
|
||||
- 2.22.5
|
||||
- 2.23.0
|
||||
- 2.25.1
|
||||
- 2.30.8
|
||||
- latest # We rely on github to have the latest version installed on their VMs
|
||||
runs-on: ubuntu-latest
|
||||
name: "Integration Tests"
|
||||
name: "Integration Tests - git ${{matrix.git-version}}"
|
||||
env:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Restore Git cache
|
||||
if: matrix.git-version != 'latest'
|
||||
id: cache-git-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ~/git-${{matrix.git-version}}
|
||||
key: ${{runner.os}}-git-${{matrix.git-version}}
|
||||
- name: Build Git ${{matrix.git-version}}
|
||||
if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
|
||||
run: >
|
||||
sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential ca-certificates curl gettext libexpat1-dev libssl-dev libz-dev openssl
|
||||
&& curl -sL "https://mirrors.edge.kernel.org/pub/software/scm/git/git-${{matrix.git-version}}.tar.xz" -o - | tar xJ -C "$HOME"
|
||||
&& cd "$HOME/git-${{matrix.git-version}}"
|
||||
&& ./configure
|
||||
&& make -j
|
||||
- name: Install Git ${{matrix.git-version}}
|
||||
if: matrix.git-version != 'latest'
|
||||
run: sudo make -C "$HOME/git-${{matrix.git-version}}" -j install
|
||||
- name: Save Git cache
|
||||
if: steps.cache-git-restore.outputs.cache-hit != 'true' && matrix.git-version != 'latest'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: ~/git-${{matrix.git-version}}
|
||||
key: ${{runner.os}}-git-${{matrix.git-version}}
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
@@ -67,10 +101,11 @@ jobs:
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Print git version
|
||||
run: git --version
|
||||
- name: Test code
|
||||
# LONG_WAIT_BEFORE_FAIL means that for a given test assertion, we'll wait longer before failing
|
||||
run: |
|
||||
LONG_WAIT_BEFORE_FAIL=true go test pkg/integration/clients/*.go
|
||||
./scripts/run_integration_tests.sh
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@@ -78,7 +113,7 @@ jobs:
|
||||
GOARCH: amd64
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
@@ -114,7 +149,7 @@ jobs:
|
||||
GOARCH: amd64
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
@@ -148,7 +183,7 @@ jobs:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
@@ -169,3 +204,14 @@ jobs:
|
||||
- name: errors
|
||||
run: golangci-lint run
|
||||
if: ${{ failure() }}
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
# permissions:
|
||||
# issues: write
|
||||
# pull-requests: write
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
mode: exactly
|
||||
count: 1
|
||||
labels: "ignore-for-release, feature, enhancement, maintenance, docs, i18n"
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1.0.8
|
||||
|
||||
+11
-5
@@ -27,7 +27,7 @@ gui:
|
||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||
expandFocusedSidePanel: false
|
||||
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko'
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
|
||||
shortTimeFormat: '3:04PM'
|
||||
theme:
|
||||
@@ -36,6 +36,9 @@ gui:
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- white
|
||||
searchingActiveBorderColor:
|
||||
- cyan
|
||||
- bold
|
||||
optionsTextColor:
|
||||
- blue
|
||||
selectedLineBgColor:
|
||||
@@ -53,7 +56,7 @@ gui:
|
||||
commitLength:
|
||||
show: true
|
||||
mouseEvents: true
|
||||
skipUnstageLineWarning: false
|
||||
skipDiscardChangeWarning: false
|
||||
skipStashWarning: false
|
||||
showFileTree: true # for rendering changes files in a tree format
|
||||
showListFooter: true # for seeing the '5 of 20' message in list panels
|
||||
@@ -62,7 +65,8 @@ gui:
|
||||
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
|
||||
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
|
||||
showCommandLog: true
|
||||
showIcons: false
|
||||
showIcons: false # deprecated: use nerdFontsVersion instead
|
||||
nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
|
||||
commandLogSize: 8
|
||||
splitDiff: 'auto' # one of 'auto' | 'always'
|
||||
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
||||
@@ -73,7 +77,6 @@ git:
|
||||
useConfig: false
|
||||
commit:
|
||||
signOff: false
|
||||
verbose: default # one of 'default' | 'always' | 'never'
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
@@ -420,9 +423,12 @@ If you are using [Nerd Fonts](https://www.nerdfonts.com), you can display icons.
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
showIcons: true
|
||||
nerdFontsVersion: "3"
|
||||
```
|
||||
|
||||
Supported versions are "2" and "3". The deprecated config `showIcons` sets the
|
||||
version to "2" for backwards compatibility.
|
||||
|
||||
## Keybindings
|
||||
|
||||
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
|
||||
|
||||
@@ -59,6 +59,12 @@ For a given custom command, here are the allowed fields:
|
||||
| description | Label for the custom command when displayed in the keybindings menu | no |
|
||||
| stream | Whether you want to stream the command's output to the Command Log panel | no |
|
||||
| showOutput | Whether you want to show the command's output in a popup within Lazygit | no |
|
||||
| after | Actions to take after the command has completed | no |
|
||||
|
||||
Here are the options for the `after` key:
|
||||
| _field_ | _description_ | required |
|
||||
|-----------------|----------------------|-|
|
||||
| checkForConflicts | true/false. If true, check for merge conflicts | no |
|
||||
|
||||
## Contexts
|
||||
|
||||
@@ -101,7 +107,7 @@ These fields are applicable to all prompts.
|
||||
The permitted suggestions fields are:
|
||||
| _field_ | _description_ | _required_ |
|
||||
|-----------------|----------------------|-|
|
||||
| preset | Uses built-in logic to obtain the suggestions. One of 'files', 'branches', 'remotes', 'remoteBranches', 'refs' | no |
|
||||
| preset | Uses built-in logic to obtain the suggestions. One of 'authors', 'branches', 'files', 'refs', 'remotes', 'remoteBranches', 'tags' | no |
|
||||
| command | Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field. | no |
|
||||
|
||||
Here's an example of passing a preset:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
see new docs [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
# Documentation Overview
|
||||
# Documentation Overview
|
||||
|
||||
* [Configuration](./Config.md).
|
||||
* [Custom Commands](./Custom_Command_Keybindings.md)
|
||||
* [Custom Pagers](./Custom_Pagers.md)
|
||||
* [Keybindings](./keybindings)
|
||||
* [Undo/Redo](./Undoing.md)
|
||||
* [Searching/Filtering](./Searching.md)
|
||||
* [Dev docs](./dev)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Searching/Filtering
|
||||
|
||||
## View searching/filtering
|
||||
|
||||
Depending on the currently focused view, hitting '/' will bring up a filter or search prompt. When filtering, the contents of the view will be filtered down to only those lines which match the query string. When searching, the contents of the view are not filtered, but matching lines are highlighted and you can iterate through matches with `n`/`N`.
|
||||
|
||||
We intend to support filtering for the files view soon, but at the moment it uses searching. We intend to continue using search for the commits view because you typically care about the commits that come before/after a matching commit.
|
||||
|
||||
If you would like both filtering and searching to be enabled on a given view, please raise an issue for this.
|
||||
|
||||
## Filtering files by status
|
||||
|
||||
You can filter the files view to only show staged/unstaged files by pressing `<c-b>` in the files view.
|
||||
|
||||
## Filtering commits by file path
|
||||
|
||||
You can filter the commits view to only show commits which contain changes to a given file path.
|
||||
|
||||
You can do this in a couple of ways:
|
||||
1) Start lazygit with the -f flag e.g. `lazygit -f my/path`
|
||||
2) From within lazygit, press `<c-s>` and then enter the path of the file you want to filter by
|
||||
@@ -0,0 +1,78 @@
|
||||
# Knowing when Lazygit is busy/idle
|
||||
|
||||
## The use-case
|
||||
|
||||
This topic deserves its own doc because there there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
1) press a key
|
||||
2) wait until Lazygit is idle
|
||||
3) run assertion / press another key
|
||||
4) repeat
|
||||
|
||||
In the past the process was:
|
||||
1) press a key
|
||||
2) run assertion
|
||||
3) if assertion fails, wait a bit and retry
|
||||
4) repeat
|
||||
|
||||
The old process was problematic because an assertion may give a false positive due to the contents of some view not yet having changed since the last key was pressed.
|
||||
|
||||
## The solution
|
||||
|
||||
First, it's important to distinguish three different types of goroutines:
|
||||
* The UI goroutine, of which there is only one, which infinitely processes a queue of events
|
||||
* Worker goroutines, which do some work and then typically enqueue an event in the UI goroutine to display the results
|
||||
* Background goroutines, which periodically spawn worker goroutines (e.g. doing a git fetch every minute)
|
||||
|
||||
The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program!
|
||||
|
||||
In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are using the `Task` type. A task represents some work being done by lazygit. The gocui Gui struct holds a map of tasks and allows creating a new task (which adds it to the map), pausing/continuing a task, and marking a task as done (which removes it from the map). Lazygit is considered to be busy so long as there is at least one busy task in the map; otherwise it's considered idle. When Lazygit goes from busy to idle, it notifies the integration test.
|
||||
|
||||
It's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps.
|
||||
|
||||
### Spawning a worker goroutine
|
||||
|
||||
Here's the basic implementation of `OnWorker` (using the same flow as `WaitGroup`s):
|
||||
|
||||
```go
|
||||
func (g *Gui) OnWorker(f func(*Task)) {
|
||||
task := g.NewTask()
|
||||
go func() {
|
||||
f(task)
|
||||
task.Done()
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
The crucial thing here is that we create the task _before_ spawning the goroutine, because it means that we'll have at least one busy task in the map until the completion of the goroutine. If we created the task within the goroutine, the current function could exit and Lazygit would be considered idle before the goroutine starts, leading to our integration test prematurely progressing.
|
||||
|
||||
You typically invoke this with `self.c.OnWorker(f)`. Note that the callback function receives the task. This allows the callback to pause/continue the task (see below).
|
||||
|
||||
### Spawning a background goroutine
|
||||
|
||||
Spawning a background goroutine is as simple as:
|
||||
|
||||
```go
|
||||
go utils.Safe(f)
|
||||
```
|
||||
|
||||
Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics.
|
||||
|
||||
### Programmatically enqueing a UI event
|
||||
|
||||
This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`.
|
||||
|
||||
### Pressing a key
|
||||
|
||||
If the user presses a key, an event will be enqueued automatically and a task will be created before (and `Done`'d after) the event is processed.
|
||||
|
||||
## Special cases
|
||||
|
||||
There are a couple of special cases where we manually pause/continue the task directly in the client code. These are subject to change but for the sake of completeness:
|
||||
|
||||
### Writing to the main view(s)
|
||||
|
||||
If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we create our own task and call `Done` on it as soon as the viewport is filled.
|
||||
|
||||
### Requesting credentials from a git command
|
||||
|
||||
Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a worker goroutine and manually pause continue its task as we go from waiting on the git command to waiting on user input. This requires passing the task through to the `Push` method so that it can be paused/continued.
|
||||
@@ -0,0 +1 @@
|
||||
see new docs [here](../../pkg/integration/README.md)
|
||||
@@ -0,0 +1,4 @@
|
||||
# Dev Documentation Overview
|
||||
|
||||
* [Busy/Idle tracking](./Busy.md).
|
||||
* [Integration Tests](../../pkg/integration/README.md)
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Previous page
|
||||
<kbd>.</kbd>: Next page
|
||||
<kbd><</kbd>: Scroll to top
|
||||
<kbd>/</kbd>: Start search
|
||||
<kbd>></kbd>: Scroll to bottom
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Next tab
|
||||
@@ -56,6 +56,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Commit summary
|
||||
@@ -96,6 +97,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Copy commit (cherry-pick)
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Confirmation panel
|
||||
@@ -111,7 +113,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Copy the file name to the clipboard
|
||||
<kbd>d</kbd>: View 'discard changes' options
|
||||
<kbd><space></kbd>: Toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Commit changes
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: Amend last commit
|
||||
@@ -129,6 +131,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Local branches
|
||||
@@ -152,6 +155,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (merging)
|
||||
@@ -190,6 +194,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Edit file
|
||||
<kbd><space></kbd>: Add/Remove line(s) to patch
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (staging)
|
||||
@@ -206,11 +211,12 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><esc></kbd>: Return to files panel
|
||||
<kbd><tab></kbd>: Switch to other panel (staged/unstaged changes)
|
||||
<kbd><space></kbd>: Toggle line staged / unstaged
|
||||
<kbd>d</kbd>: Delete change (git reset)
|
||||
<kbd>d</kbd>: Discard change (git reset)
|
||||
<kbd>E</kbd>: Edit hunk
|
||||
<kbd>c</kbd>: Commit changes
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: Commit changes using git editor
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@@ -218,6 +224,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Execute
|
||||
<kbd><esc></kbd>: Close
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@@ -233,6 +240,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@@ -245,9 +253,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: Delete branch
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: Return to remotes list
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -257,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Add new remote
|
||||
<kbd>d</kbd>: Remove remote
|
||||
<kbd>e</kbd>: Edit remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@@ -268,6 +277,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: New branch
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@@ -293,6 +303,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Copy commit range (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@@ -306,6 +317,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialize submodule
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 前のページ
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>]</kbd>: 次のタブ
|
||||
@@ -53,6 +53,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>r</kbd>: Stashを変更
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
@@ -68,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミット
|
||||
@@ -101,6 +103,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: コミットをコピー (cherry-pick)
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミットファイル
|
||||
@@ -115,6 +118,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## コミットメッセージ
|
||||
@@ -135,6 +139,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: サブモジュールのURLを更新
|
||||
<kbd>i</kbd>: サブモジュールを初期化
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## ステータス
|
||||
@@ -156,6 +161,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: タグを作成
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## ファイル
|
||||
@@ -182,6 +188,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
<kbd>M</kbd>: Git mergetoolを開く
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## ブランチ
|
||||
@@ -205,6 +212,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: ブランチ名を変更
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## メインパネル (Merging)
|
||||
@@ -243,6 +251,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd><space></kbd>: 行をパッチに追加/削除
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## メインパネル (Staging)
|
||||
@@ -264,6 +273,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 変更をコミット
|
||||
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
|
||||
<kbd>C</kbd>: gitエディタを使用して変更をコミット
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
</pre>
|
||||
|
||||
## メニュー
|
||||
@@ -271,6 +281,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 実行
|
||||
<kbd><esc></kbd>: 閉じる
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## リモート
|
||||
@@ -280,6 +291,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: リモートを新規追加
|
||||
<kbd>d</kbd>: リモートを削除
|
||||
<kbd>e</kbd>: リモートを編集
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## リモートブランチ
|
||||
@@ -292,9 +304,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: ブランチを削除
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: リモート一覧に戻る
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 参照ログ
|
||||
@@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 確認パネル
|
||||
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 이전 페이지
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>]</kbd>: 이전 탭
|
||||
@@ -57,6 +57,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@@ -68,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
@@ -83,6 +85,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 메뉴
|
||||
@@ -90,6 +93,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 실행
|
||||
<kbd><esc></kbd>: 닫기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Merging)
|
||||
@@ -128,6 +132,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd><space></kbd>: Line(s)을 패치에 추가/삭제
|
||||
<kbd><esc></kbd>: Exit custom patch builder
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Staging)
|
||||
@@ -149,6 +154,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 커밋 변경내용
|
||||
<kbd>w</kbd>: Commit changes without pre-commit hook
|
||||
<kbd>C</kbd>: Git 편집기를 사용하여 변경 내용을 커밋합니다.
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 브랜치
|
||||
@@ -172,6 +178,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: 브랜치 이름 변경
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 상태
|
||||
@@ -195,6 +202,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 서브모듈의 URL을 수정
|
||||
<kbd>i</kbd>: 서브모듈 초기화
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 원격
|
||||
@@ -204,6 +212,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 새로운 Remote 추가
|
||||
<kbd>d</kbd>: Remote를 삭제
|
||||
<kbd>e</kbd>: Remote를 수정
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 원격 브랜치
|
||||
@@ -216,9 +225,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
|
||||
<kbd>d</kbd>: 브랜치 삭제
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: 원격목록으로 돌아가기
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 커밋
|
||||
@@ -252,6 +261,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 커밋을 복사 (cherry-pick)
|
||||
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
|
||||
<kbd><enter></kbd>: View selected item's files
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 커밋 파일
|
||||
@@ -266,6 +276,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 커밋메시지
|
||||
@@ -284,6 +295,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 태그를 생성
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 파일
|
||||
@@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
<kbd>M</kbd>: Git mergetool를 열기
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
</pre>
|
||||
|
||||
## 확인 패널
|
||||
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Vorige pagina
|
||||
<kbd>.</kbd>: Volgende pagina
|
||||
<kbd><</kbd>: Scroll naar boven
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
<kbd>></kbd>: Scroll naar beneden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Volgende tabblad
|
||||
@@ -50,7 +50,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Kopieer de bestandsnaam naar het klembord
|
||||
<kbd>d</kbd>: Bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd><space></kbd>: Toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: Wijzig laatste commit
|
||||
@@ -68,6 +68,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle bestandsboom weergave
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Fetch
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Bevestigingspaneel
|
||||
@@ -98,6 +99,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Hernoem branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Commit bericht
|
||||
@@ -119,6 +121,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter bestand om geselecteerde regels toe te voegen aan de patch
|
||||
<kbd>`</kbd>: Toggle bestandsboom weergave
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
@@ -152,6 +155,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Kopieer commit (cherry-pick)
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@@ -159,6 +163,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Uitvoeren
|
||||
<kbd><esc></kbd>: Sluiten
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Mergen
|
||||
@@ -197,6 +202,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Verander bestand
|
||||
<kbd><space></kbd>: Voeg toe/verwijder lijn(en) in patch
|
||||
<kbd><esc></kbd>: Sluit lijn-bij-lijn modus
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@@ -212,6 +218,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@@ -224,9 +231,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Rebase branch
|
||||
<kbd>d</kbd>: Verwijder branch
|
||||
<kbd>u</kbd>: Stel in als upstream van uitgecheckte branch
|
||||
<kbd><esc></kbd>: Ga terug naar remotes lijst
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -236,6 +243,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Voeg een nieuwe remote toe
|
||||
<kbd>d</kbd>: Verwijder remote
|
||||
<kbd>e</kbd>: Wijzig remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Staging
|
||||
@@ -257,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: Commit veranderingen zonder pre-commit hook
|
||||
<kbd>C</kbd>: Commit veranderingen met de git editor
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
@@ -268,6 +277,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nieuwe branch
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@@ -293,6 +303,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopieer commit reeks (cherry-pick)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: Bekijk gecommite bestanden
|
||||
<kbd>/</kbd>: Start met zoeken
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@@ -306,6 +317,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialiseer submodule
|
||||
<kbd>b</kbd>: Bekijk bulk submodule opties
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Creëer tag
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: Previous page
|
||||
<kbd>.</kbd>: Next page
|
||||
<kbd><</kbd>: Scroll to top
|
||||
<kbd>/</kbd>: Start search
|
||||
<kbd>></kbd>: Scroll to bottom
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
<kbd>H</kbd>: Scroll left
|
||||
<kbd>L</kbd>: Scroll right
|
||||
<kbd>]</kbd>: Next tab
|
||||
@@ -82,6 +82,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: Kopiuj commit (przebieranie)
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Confirmation panel
|
||||
@@ -112,6 +113,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Main panel (patch building)
|
||||
@@ -127,6 +129,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Edytuj plik
|
||||
<kbd><space></kbd>: Add/Remove line(s) to patch
|
||||
<kbd><esc></kbd>: Wyście z trybu "linia po linii"
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
@@ -134,6 +137,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Wykonaj
|
||||
<kbd><esc></kbd>: Zamknij
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
@@ -142,7 +146,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: Copy the file name to the clipboard
|
||||
<kbd>d</kbd>: Pokaż opcje porzucania zmian
|
||||
<kbd><space></kbd>: Przełącz stan poczekalni
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: Zatwierdź zmiany
|
||||
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
|
||||
<kbd>A</kbd>: Zmień ostatni commit
|
||||
@@ -160,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>M</kbd>: Open external merge tool (git mergetool)
|
||||
<kbd>f</kbd>: Pobierz
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Pliki commita
|
||||
@@ -174,6 +179,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: Enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: Toggle file tree view
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Poczekalnia
|
||||
@@ -190,11 +196,12 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><esc></kbd>: Wróć do panelu plików
|
||||
<kbd><tab></kbd>: Switch to other panel (staged/unstaged changes)
|
||||
<kbd><space></kbd>: Toggle line staged / unstaged
|
||||
<kbd>d</kbd>: Delete change (git reset)
|
||||
<kbd>d</kbd>: Discard change (git reset)
|
||||
<kbd>E</kbd>: Edit hunk
|
||||
<kbd>c</kbd>: Zatwierdź zmiany
|
||||
<kbd>w</kbd>: Zatwierdź zmiany bez skryptu pre-commit
|
||||
<kbd>C</kbd>: Zatwierdź zmiany używając edytora
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
@@ -210,6 +217,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remote branches
|
||||
@@ -222,9 +230,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: Zmiana bazy gałęzi
|
||||
<kbd>d</kbd>: Usuń gałąź
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd><esc></kbd>: Wróć do listy repozytoriów zdalnych
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -234,6 +242,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Add new remote
|
||||
<kbd>d</kbd>: Remove remote
|
||||
<kbd>e</kbd>: Edit remote
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Scalanie
|
||||
@@ -261,6 +270,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nowa gałąź
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
@@ -286,6 +296,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: Kopiuj zakres commitów (przebieranie)
|
||||
<kbd><c-r></kbd>: Reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: Przeglądaj pliki commita
|
||||
<kbd>/</kbd>: Search the current view by text
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
@@ -299,6 +310,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: Update submodule URL
|
||||
<kbd>i</kbd>: Initialize submodule
|
||||
<kbd>b</kbd>: View bulk submodule options
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Tags
|
||||
@@ -310,6 +322,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Zwykłe
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
|
||||
# Lazygit Связки клавиш
|
||||
|
||||
_Связки клавиш_
|
||||
|
||||
## Глобальные сочетания клавиш
|
||||
|
||||
<pre>
|
||||
<kbd><c-r></kbd>: Переключиться на последний репозиторий
|
||||
<kbd><pgup></kbd>: Прокрутить вверх главную панель (fn+up/shift+k)
|
||||
<kbd><pgdown></kbd>: Прокрутить вниз главную панель (fn+down/shift+j)
|
||||
<kbd>@</kbd>: Открыть меню журнала команд
|
||||
<kbd>}</kbd>: Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
|
||||
<kbd>{</kbd>: Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении
|
||||
<kbd>:</kbd>: Выполнить пользовательскую команду
|
||||
<kbd><c-p></kbd>: Просмотреть пользовательские параметры патча
|
||||
<kbd>m</kbd>: Просмотреть параметры слияния/перебазирования
|
||||
<kbd>R</kbd>: Обновить
|
||||
<kbd>+</kbd>: Следующий режим экрана (нормальный/полуэкранный/полноэкранный)
|
||||
<kbd>_</kbd>: Предыдущий режим экрана
|
||||
<kbd>?</kbd>: Открыть меню
|
||||
<kbd><c-s></kbd>: Просмотреть параметры фильтрации по пути
|
||||
<kbd>W</kbd>: Открыть меню сравнении
|
||||
<kbd><c-e></kbd>: Открыть меню сравнении
|
||||
<kbd><c-w></kbd>: Переключить отображение изменении пробелов в просмотрщике сравнении
|
||||
<kbd>z</kbd>: Отменить (через reflog) (экспериментальный)
|
||||
<kbd><c-z></kbd>: Повторить (через reflog) (экспериментальный)
|
||||
<kbd>P</kbd>: Отправить изменения
|
||||
<kbd>p</kbd>: Получить и слить изменения
|
||||
</pre>
|
||||
|
||||
## Навигация по панели списка
|
||||
|
||||
<pre>
|
||||
<kbd>,</kbd>: Предыдущая страница
|
||||
<kbd>.</kbd>: Следующая страница
|
||||
<kbd><</kbd>: Пролистать наверх
|
||||
<kbd>></kbd>: Прокрутить вниз
|
||||
<kbd>/</kbd>: Найти
|
||||
<kbd>H</kbd>: Прокрутить влево
|
||||
<kbd>L</kbd>: Прокрутить вправо
|
||||
<kbd>]</kbd>: Следующая вкладка
|
||||
<kbd>[</kbd>: Предыдущая вкладка
|
||||
</pre>
|
||||
|
||||
## Главная панель (Индексирование)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: Выбрать предыдущую часть
|
||||
<kbd><right></kbd>: Выбрать следующую часть
|
||||
<kbd>v</kbd>: Переключить выборку перетаскивания
|
||||
<kbd>V</kbd>: Переключить выборку перетаскивания
|
||||
<kbd>a</kbd>: Переключить выборку частей
|
||||
<kbd><c-o></kbd>: Скопировать выделенный текст в буфер обмена
|
||||
<kbd>o</kbd>: Открыть файл
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd><esc></kbd>: Вернуться к панели файлов
|
||||
<kbd><tab></kbd>: Переключиться на другую панель (проиндексированные/непроиндексированные изменения)
|
||||
<kbd><space></kbd>: Переключить строку в проиндексированные / непроиндексированные
|
||||
<kbd>d</kbd>: Отменить изменение (git reset)
|
||||
<kbd>E</kbd>: Изменить эту часть
|
||||
<kbd>c</kbd>: Сохранить изменения
|
||||
<kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
|
||||
<kbd>C</kbd>: Сохранить изменения с помощью редактора git
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Главная панель (Обычный)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: Прокрутить вниз (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: Прокрутить вверх (fn+down)
|
||||
</pre>
|
||||
|
||||
## Главная панель (Слияние)
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd>o</kbd>: Открыть файл
|
||||
<kbd><left></kbd>: Выбрать предыдущий конфликт
|
||||
<kbd><right></kbd>: Выбрать следующий конфликт
|
||||
<kbd><up></kbd>: Выбрать предыдущую часть
|
||||
<kbd><down></kbd>: Выбрать следующую часть
|
||||
<kbd>z</kbd>: Отменить
|
||||
<kbd>M</kbd>: Открыть внешний инструмент слияния (git mergetool)
|
||||
<kbd><space></kbd>: Выбрать эту часть
|
||||
<kbd>b</kbd>: Выбрать все части
|
||||
<kbd><esc></kbd>: Вернуться к панели файлов
|
||||
</pre>
|
||||
|
||||
## Главная панель (сборка патчей)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: Выбрать предыдущую часть
|
||||
<kbd><right></kbd>: Выбрать следующую часть
|
||||
<kbd>v</kbd>: Переключить выборку перетаскивания
|
||||
<kbd>V</kbd>: Переключить выборку перетаскивания
|
||||
<kbd>a</kbd>: Переключить выборку частей
|
||||
<kbd><c-o></kbd>: Скопировать выделенный текст в буфер обмена
|
||||
<kbd>o</kbd>: Открыть файл
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd><space></kbd>: Добавить/удалить строку(и) для патча
|
||||
<kbd><esc></kbd>: Выйти из сборщика пользовательских патчей
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Журнал ссылок (Reflog)
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать SHA коммита в буфер обмена
|
||||
<kbd><space></kbd>: Переключить коммит
|
||||
<kbd>y</kbd>: Скопировать атрибут коммита
|
||||
<kbd>o</kbd>: Открыть коммит в браузере
|
||||
<kbd>n</kbd>: Создать новую ветку с этого коммита
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><c-r></kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Коммиты
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать SHA коммита в буфер обмена
|
||||
<kbd><c-r></kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
|
||||
<kbd>b</kbd>: Просмотреть параметры бинарного поиска
|
||||
<kbd>s</kbd>: Объединить несколько коммитов в один нижний
|
||||
<kbd>f</kbd>: Объединить несколько коммитов в один отбросив сообщение коммита
|
||||
<kbd>r</kbd>: Перефразировать коммит
|
||||
<kbd>R</kbd>: Переписать коммит с помощью редактора
|
||||
<kbd>d</kbd>: Удалить коммит
|
||||
<kbd>e</kbd>: Изменить коммит
|
||||
<kbd>p</kbd>: Выбрать коммит (в середине перебазирования)
|
||||
<kbd>F</kbd>: Создать fixup коммит для этого коммита
|
||||
<kbd>S</kbd>: Объединить все 'fixup!' коммиты выше в выбранный коммит (автосохранение)
|
||||
<kbd><c-j></kbd>: Переместить коммит вниз на один
|
||||
<kbd><c-k></kbd>: Переместить коммит вверх на один
|
||||
<kbd>v</kbd>: Вставить отобранные коммиты (cherry-pick)
|
||||
<kbd>A</kbd>: Править последний коммит с проиндексированными изменениями
|
||||
<kbd>a</kbd>: Установить/убрать автора коммита
|
||||
<kbd>t</kbd>: Отменить коммит
|
||||
<kbd>T</kbd>: Пометить коммит тегом
|
||||
<kbd><c-l></kbd>: Открыть меню журнала
|
||||
<kbd><space></kbd>: Переключить коммит
|
||||
<kbd>y</kbd>: Скопировать атрибут коммита
|
||||
<kbd>o</kbd>: Открыть коммит в браузере
|
||||
<kbd>n</kbd>: Создать новую ветку с этого коммита
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Локальные Ветки
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать название ветки в буфер обмена
|
||||
<kbd>i</kbd>: Показать параметры git-flow
|
||||
<kbd><space></kbd>: Переключить
|
||||
<kbd>n</kbd>: Новая ветка
|
||||
<kbd>o</kbd>: Создать запрос на принятие изменений
|
||||
<kbd>O</kbd>: Создать параметры запроса принятие изменений
|
||||
<kbd><c-y></kbd>: Скопировать URL запроса на принятие изменений в буфер обмена
|
||||
<kbd>c</kbd>: Переключить по названию
|
||||
<kbd>F</kbd>: Принудительное переключение
|
||||
<kbd>d</kbd>: Удалить ветку
|
||||
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
|
||||
<kbd>M</kbd>: Слияние с текущей переключённой веткой
|
||||
<kbd>f</kbd>: Перемотать эту ветку вперёд из её upstream-ветки
|
||||
<kbd>T</kbd>: Создать тег
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>R</kbd>: Переименовать ветку
|
||||
<kbd>u</kbd>: Установить/убрать upstream-ветку
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Меню
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Выполнить
|
||||
<kbd><esc></kbd>: Закрыть
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Панель Подтверждения
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Подтвердить
|
||||
<kbd><esc></kbd>: Закрыть/отменить
|
||||
</pre>
|
||||
|
||||
## Подкоммиты
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать SHA коммита в буфер обмена
|
||||
<kbd><space></kbd>: Переключить коммит
|
||||
<kbd>y</kbd>: Скопировать атрибут коммита
|
||||
<kbd>o</kbd>: Открыть коммит в браузере
|
||||
<kbd>n</kbd>: Создать новую ветку с этого коммита
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>c</kbd>: Скопировать отобранные коммит (cherry-pick)
|
||||
<kbd>C</kbd>: Скопировать несколько отобранных коммитов (cherry-pick)
|
||||
<kbd><c-r></kbd>: Сбросить отобранную (скопированную | cherry-picked) выборку коммитов
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Подмодули
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать название подмодуля в буфер обмена
|
||||
<kbd><enter></kbd>: Ввести подмодуль
|
||||
<kbd>d</kbd>: Удалить подмодуль
|
||||
<kbd>u</kbd>: Обновить подмодуль
|
||||
<kbd>n</kbd>: Добавить новый подмодуль
|
||||
<kbd>e</kbd>: Обновить URL подмодуля
|
||||
<kbd>i</kbd>: Инициализировать подмодуль
|
||||
<kbd>b</kbd>: Просмотреть параметры массового подмодуля
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Сводка коммита
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: Подтвердить
|
||||
<kbd><esc></kbd>: Закрыть
|
||||
</pre>
|
||||
|
||||
## Сохранить Изменения Файлов
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать закомиченное имя файла в буфер обмена
|
||||
<kbd>c</kbd>: Переключить файл
|
||||
<kbd>d</kbd>: Отменить изменения коммита в этом файле
|
||||
<kbd>o</kbd>: Открыть файл
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd><space></kbd>: Переключить файлы включённые в патч
|
||||
<kbd>a</kbd>: Переключить все файлы, включённые в патч
|
||||
<kbd><enter></kbd>: Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения)
|
||||
<kbd>`</kbd>: Переключить вид дерева файлов
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Статус
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: Открыть файл конфигурации
|
||||
<kbd>e</kbd>: Редактировать файл конфигурации
|
||||
<kbd>u</kbd>: Проверить обновления
|
||||
<kbd><enter></kbd>: Переключиться на последний репозиторий
|
||||
<kbd>a</kbd>: Показать все логи ветки
|
||||
</pre>
|
||||
|
||||
## Теги
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Переключить
|
||||
<kbd>d</kbd>: Удалить тег
|
||||
<kbd>P</kbd>: Отправить тег
|
||||
<kbd>n</kbd>: Создать тег
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Удалённые ветки
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать название ветки в буфер обмена
|
||||
<kbd><space></kbd>: Переключить
|
||||
<kbd>n</kbd>: Новая ветка
|
||||
<kbd>M</kbd>: Слияние с текущей переключённой веткой
|
||||
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
|
||||
<kbd>d</kbd>: Удалить ветку
|
||||
<kbd>u</kbd>: Установить как upstream-ветку переключённую ветку
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Удалённые репозитории
|
||||
|
||||
<pre>
|
||||
<kbd>f</kbd>: Получение изменения из удалённого репозитория
|
||||
<kbd>n</kbd>: Добавить новую удалённую ветку
|
||||
<kbd>d</kbd>: Удалить удалённую ветку
|
||||
<kbd>e</kbd>: Редактировать удалённый репозитории
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## Файлы
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: Скопировать название файла в буфер обмена
|
||||
<kbd>d</kbd>: Просмотреть параметры «отмены изменении»
|
||||
<kbd><space></kbd>: Переключить индекс
|
||||
<kbd><c-b></kbd>: Фильтровать файлы (проиндексированные/непроиндексированные)
|
||||
<kbd>c</kbd>: Сохранить изменения
|
||||
<kbd>w</kbd>: Закоммитить изменения без предварительного хука коммита
|
||||
<kbd>A</kbd>: Правка последнего коммита
|
||||
<kbd>C</kbd>: Сохранить изменения с помощью редактора git
|
||||
<kbd>e</kbd>: Редактировать файл
|
||||
<kbd>o</kbd>: Открыть файл
|
||||
<kbd>i</kbd>: Игнорировать или исключить файл
|
||||
<kbd>r</kbd>: Обновить файлы
|
||||
<kbd>s</kbd>: Припрятать все изменения
|
||||
<kbd>S</kbd>: Просмотреть параметры хранилища
|
||||
<kbd>a</kbd>: Все проиндексированные/непроиндексированные
|
||||
<kbd><enter></kbd>: Проиндексировать отдельные части/строки для файла или свернуть/развернуть для каталога
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса upstream-ветки
|
||||
<kbd>D</kbd>: Просмотреть параметры сброса
|
||||
<kbd>`</kbd>: Переключить вид дерева файлов
|
||||
<kbd>M</kbd>: Открыть внешний инструмент слияния (git mergetool)
|
||||
<kbd>f</kbd>: Получить изменения
|
||||
<kbd>/</kbd>: Найти
|
||||
</pre>
|
||||
|
||||
## Хранилище
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Применить припрятанные изменения
|
||||
<kbd>g</kbd>: Применить припрятанные изменения и тут же удалить их из хранилища
|
||||
<kbd>d</kbd>: Удалить припрятанные изменения из хранилища
|
||||
<kbd>n</kbd>: Новая ветка
|
||||
<kbd>r</kbd>: Переименовать хранилище
|
||||
<kbd><enter></kbd>: Просмотреть файлы выбранного элемента
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
@@ -36,8 +36,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>,</kbd>: 上一页
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>]</kbd>: 下一个标签
|
||||
@@ -57,6 +57,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 分支页面
|
||||
@@ -80,6 +81,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>R</kbd>: 重命名分支
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 子提交
|
||||
@@ -95,6 +97,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 子模块
|
||||
@@ -108,6 +111,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 更新子模块 URL
|
||||
<kbd>i</kbd>: 初始化子模块
|
||||
<kbd>b</kbd>: 查看批量子模块选项
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 提交
|
||||
@@ -141,6 +145,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 复制提交(拣选)
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 提交文件
|
||||
@@ -155,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>a</kbd>: Toggle all files included in patch
|
||||
<kbd><enter></kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 提交讯息
|
||||
@@ -170,7 +176,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-o></kbd>: 将文件名复制到剪贴板
|
||||
<kbd>d</kbd>: 查看'放弃更改'选项
|
||||
<kbd><space></kbd>: 切换暂存状态
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd><c-b></kbd>: Filter files by status
|
||||
<kbd>c</kbd>: 提交更改
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>A</kbd>: 修补最后一次提交
|
||||
@@ -188,6 +194,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd>f</kbd>: 抓取
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 构建补丁中
|
||||
@@ -203,6 +210,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd><space></kbd>: 添加/移除 行到补丁
|
||||
<kbd><esc></kbd>: 退出逐行模式
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 标签页面
|
||||
@@ -214,6 +222,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 创建标签
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 正在合并
|
||||
@@ -251,6 +260,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>c</kbd>: 提交更改
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>C</kbd>: 提交更改(使用编辑器编辑提交信息)
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
</pre>
|
||||
|
||||
## 正常
|
||||
@@ -282,6 +292,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 执行
|
||||
<kbd><esc></kbd>: 关闭
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 贮藏
|
||||
@@ -293,6 +304,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: Rename stash
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 远程分支
|
||||
@@ -305,9 +317,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>r</kbd>: 将已检出的分支变基到该分支
|
||||
<kbd>d</kbd>: 删除分支
|
||||
<kbd>u</kbd>: 设置为检出分支的上游
|
||||
<kbd><esc></kbd>: 返回远程仓库列表
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 远程页面
|
||||
@@ -317,4 +329,5 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 添加新的远程仓库
|
||||
<kbd>d</kbd>: 删除远程
|
||||
<kbd>e</kbd>: 编辑远程仓库
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
@@ -36,8 +36,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>,</kbd>: 上一頁
|
||||
<kbd>.</kbd>: 下一頁
|
||||
<kbd><</kbd>: 捲動到頂部
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
<kbd>></kbd>: 捲動到底部
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
<kbd>H</kbd>: 向左捲動
|
||||
<kbd>L</kbd>: 向右捲動
|
||||
<kbd>]</kbd>: 下一個索引標籤
|
||||
@@ -57,6 +57,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><c-r></kbd>: 重設選定的揀選 (複製) 提交
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 主視窗 (一般)
|
||||
@@ -101,6 +102,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>c</kbd>: 提交變更
|
||||
<kbd>w</kbd>: 沒有預提交 hook 就提交更改
|
||||
<kbd>C</kbd>: 使用 git 編輯器提交變更
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 主面板 (補丁生成)
|
||||
@@ -116,6 +118,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>e</kbd>: 編輯檔案
|
||||
<kbd><space></kbd>: 向 (或從) 補丁中添加/刪除行
|
||||
<kbd><esc></kbd>: 退出自訂補丁建立器
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 功能表
|
||||
@@ -123,6 +126,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 執行
|
||||
<kbd><esc></kbd>: 關閉
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 子提交
|
||||
@@ -138,6 +142,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><c-r></kbd>: 重設選定的揀選 (複製) 提交
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 子模組
|
||||
@@ -151,6 +156,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>e</kbd>: 更新子模組 URL
|
||||
<kbd>i</kbd>: 初始化子模組
|
||||
<kbd>b</kbd>: 查看批量子模組選項
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 提交
|
||||
@@ -184,6 +190,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>c</kbd>: 複製提交 (揀選)
|
||||
<kbd>C</kbd>: 複製提交範圍 (揀選)
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 提交摘要
|
||||
@@ -205,6 +212,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>a</kbd>: 切換所有檔案是否包含在補丁中
|
||||
<kbd><enter></kbd>: 輸入檔案以將選定的行添加至補丁(或切換目錄折疊)
|
||||
<kbd>`</kbd>: 切換檔案樹狀視圖
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 收藏 (Stash)
|
||||
@@ -216,6 +224,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: 重新命名收藏
|
||||
<kbd><enter></kbd>: 檢視所選項目的檔案
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 本地分支
|
||||
@@ -239,6 +248,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>R</kbd>: 重新命名分支
|
||||
<kbd>u</kbd>: 設定/取消設定上游
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 標籤
|
||||
@@ -250,6 +260,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 建立標籤
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 檔案
|
||||
@@ -276,6 +287,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>`</kbd>: 切換檔案樹狀視圖
|
||||
<kbd>M</kbd>: 開啟外部合併工具 (git mergetool)
|
||||
<kbd>f</kbd>: 擷取
|
||||
<kbd>/</kbd>: 開始搜尋
|
||||
</pre>
|
||||
|
||||
## 狀態
|
||||
@@ -302,6 +314,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 新增遠端
|
||||
<kbd>d</kbd>: 移除遠端
|
||||
<kbd>e</kbd>: 編輯遠端
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
## 遠端分支
|
||||
@@ -314,7 +327,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>r</kbd>: 將已檢出的分支變基至此分支
|
||||
<kbd>d</kbd>: 刪除分支
|
||||
<kbd>u</kbd>: 將此分支設為當前分支之上游
|
||||
<kbd><esc></kbd>: 返回遠端列表
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
</pre>
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell/v2 v2.6.0
|
||||
github.com/go-errors/errors v1.4.2
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@@ -67,8 +67,8 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -28,8 +28,8 @@ github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4 h1:fzcGaoAFDHWzJRKw//CSZFrXucsLKplIvOSab3FtWWM=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
@@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e h1:NpsrRAbYUmMkxDgNAVSlu3LxtfwdDe140vWVo/VldgA=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230601121845-cb89273fdd4e/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f h1:w/pxI34XepTAx4HwxUu8ipimbVRgSTS+7ahmgFQwH80=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@@ -206,21 +206,22 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
+11
-2
@@ -120,6 +120,11 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
return app, err
|
||||
}
|
||||
|
||||
// used for testing purposes
|
||||
if os.Getenv("SHOW_RECENT_REPOS") == "true" {
|
||||
showRecentRepos = true
|
||||
}
|
||||
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
@@ -189,7 +194,7 @@ func (app *App) setupRepo() (bool, error) {
|
||||
fmt.Print(app.Tr.InitialBranch)
|
||||
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if trimmedResponse := strings.Trim(response, " \r\n"); len(trimmedResponse) > 0 {
|
||||
initialBranchArg += "--initial-branch=" + app.OSCommand.Quote(trimmedResponse)
|
||||
initialBranchArg += "--initial-branch=" + trimmedResponse
|
||||
}
|
||||
}
|
||||
case "create":
|
||||
@@ -205,7 +210,11 @@ func (app *App) setupRepo() (bool, error) {
|
||||
}
|
||||
|
||||
if shouldInitRepo {
|
||||
if err := app.OSCommand.Cmd.New([]string{"git", "init", initialBranchArg}).Run(); err != nil {
|
||||
args := []string{"git", "init"}
|
||||
if initialBranchArg != "" {
|
||||
args = append(args, initialBranchArg)
|
||||
}
|
||||
if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -90,6 +91,15 @@ func getDaemonKind() DaemonKind {
|
||||
return DaemonKind(intValue)
|
||||
}
|
||||
|
||||
func getCommentChar() byte {
|
||||
cmd := secureexec.Command("git", "config", "--get", "--null", "core.commentChar")
|
||||
if output, err := cmd.Output(); err == nil && len(output) == 2 {
|
||||
return output[0]
|
||||
}
|
||||
|
||||
return '#'
|
||||
}
|
||||
|
||||
// An Instruction is a command to be run by lazygit in daemon mode.
|
||||
// It is serialized to json and passed to lazygit via environment variables
|
||||
type Instruction interface {
|
||||
@@ -199,7 +209,7 @@ func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
|
||||
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
for _, c := range self.Changes {
|
||||
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
|
||||
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction, getCommentChar()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -233,7 +243,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha)
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,7 +267,7 @@ func (self *MoveTodoUpInstruction) SerializedInstructions() string {
|
||||
|
||||
func (self *MoveTodoUpInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoUp(path, self.Sha, todo.Pick)
|
||||
return utils.MoveTodoUp(path, self.Sha, todo.Pick, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -281,7 +291,7 @@ func (self *MoveTodoDownInstruction) SerializedInstructions() string {
|
||||
|
||||
func (self *MoveTodoDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoDown(path, self.Sha, todo.Pick)
|
||||
return utils.MoveTodoDown(path, self.Sha, todo.Pick, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ func NewGitCommandAux(
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode, gitCommon)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock
|
||||
|
||||
const (
|
||||
WaitTime = 50 * time.Millisecond
|
||||
RetryCount = 5
|
||||
)
|
||||
|
||||
type gitCmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
innerRunner oscommands.ICmdObjRunner
|
||||
@@ -18,13 +26,44 @@ func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error {
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) {
|
||||
return self.innerRunner.RunWithOutput(cmdObj)
|
||||
var output string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
output, err = self.innerRunner.RunWithOutput(newCmdObj)
|
||||
|
||||
if err == nil || !strings.Contains(output, ".git/index.lock") {
|
||||
return output, err
|
||||
}
|
||||
|
||||
// if we have an error based on the index lock, we should wait a bit and then retry
|
||||
self.log.Warn("index.lock prevented command from running. Retrying command after a small wait")
|
||||
time.Sleep(WaitTime)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
|
||||
return self.innerRunner.RunWithOutputs(cmdObj)
|
||||
var stdout, stderr string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj)
|
||||
|
||||
if err == nil || !strings.Contains(stdout+stderr, ".git/index.lock") {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// if we have an error based on the index lock, we should wait a bit and then retry
|
||||
self.log.Warn("index.lock prevented command from running. Retrying command after a small wait")
|
||||
time.Sleep(WaitTime)
|
||||
}
|
||||
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// Retry logic not implemented here, but these commands typically don't need to obtain a lock.
|
||||
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ func (self *CommitCommands) commitMessageArgs(message string) []string {
|
||||
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||
ArgIf(self.verboseFlag() != "", self.verboseFlag()).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
@@ -109,17 +108,6 @@ func (self *CommitCommands) signoffFlag() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitCommands) verboseFlag() string {
|
||||
switch self.config.UserConfig.Git.Commit.Verbose {
|
||||
case "always":
|
||||
return "--verbose"
|
||||
case "never":
|
||||
return "--no-verbose"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Get the subject of the HEAD commit
|
||||
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
|
||||
cmdArgs := NewGitCmd("log").Arg("-1", "--pretty=%s").ToArgv()
|
||||
@@ -212,6 +200,7 @@ func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhit
|
||||
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
Arg("--stat").
|
||||
Arg("--decorate").
|
||||
Arg("-p").
|
||||
Arg(sha).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
|
||||
@@ -38,6 +38,7 @@ type CommitLoader struct {
|
||||
// When nil, we're yet to obtain the list of existing main branches.
|
||||
// When an empty slice, we've obtained the list and it's empty.
|
||||
mainBranches []string
|
||||
*GitCommon
|
||||
}
|
||||
|
||||
// making our dependencies explicit for the sake of easier testing
|
||||
@@ -46,6 +47,7 @@ func NewCommitLoader(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dotGitDir string,
|
||||
getRebaseMode func() (enums.RebaseMode, error),
|
||||
gitCommon *GitCommon,
|
||||
) *CommitLoader {
|
||||
return &CommitLoader{
|
||||
Common: cmn,
|
||||
@@ -55,6 +57,7 @@ func NewCommitLoader(
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
mainBranches: nil,
|
||||
GitCommon: gitCommon,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,11 +162,17 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
tags := []string{}
|
||||
|
||||
if extraInfo != "" {
|
||||
re := regexp.MustCompile(`tag: ([^,\)]+)`)
|
||||
tagMatch := re.FindStringSubmatch(extraInfo)
|
||||
if len(tagMatch) > 1 {
|
||||
tags = append(tags, tagMatch[1])
|
||||
extraInfoFields := strings.Split(extraInfo, ",")
|
||||
for _, extraInfoField := range extraInfoFields {
|
||||
extraInfoField = strings.TrimSpace(extraInfoField)
|
||||
re := regexp.MustCompile(`tag: (.+)`)
|
||||
tagMatch := re.FindStringSubmatch(extraInfoField)
|
||||
if len(tagMatch) > 1 {
|
||||
tags = append(tags, tagMatch[1])
|
||||
}
|
||||
}
|
||||
|
||||
extraInfo = "(" + extraInfo + ")"
|
||||
}
|
||||
|
||||
unitTimestampInt, _ := strconv.Atoi(unixTimestamp)
|
||||
@@ -219,11 +228,24 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
return nil, err
|
||||
}
|
||||
|
||||
findFullCommit := lo.Ternary(self.version.IsOlderThan(2, 25, 2),
|
||||
func(sha string) *models.Commit {
|
||||
for s, c := range fullCommits {
|
||||
if strings.HasPrefix(s, sha) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(sha string) *models.Commit {
|
||||
return fullCommits[sha]
|
||||
})
|
||||
|
||||
hydratedCommits := make([]*models.Commit, 0, len(commits))
|
||||
for _, rebasingCommit := range commits {
|
||||
if rebasingCommit.Sha == "" {
|
||||
hydratedCommits = append(hydratedCommits, rebasingCommit)
|
||||
} else if commit := fullCommits[rebasingCommit.Sha]; commit != nil {
|
||||
} else if commit := findFullCommit(rebasingCommit.Sha); commit != nil {
|
||||
commit.Action = rebasingCommit.Action
|
||||
commit.Status = rebasingCommit.Status
|
||||
hydratedCommits = append(hydratedCommits, commit)
|
||||
@@ -304,12 +326,23 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
|
||||
|
||||
commits := []*models.Commit{}
|
||||
|
||||
todos, err := todo.Parse(bytes.NewBuffer(bytesContent))
|
||||
todos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar())
|
||||
if err != nil {
|
||||
self.Log.Error(fmt.Sprintf("error occurred while parsing git-rebase-todo file: %s", err.Error()))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// See if the current commit couldn't be applied because it conflicted; if
|
||||
// so, add a fake entry for it
|
||||
if conflictedCommitSha := self.getConflictedCommit(todos); conflictedCommitSha != "" {
|
||||
commits = append(commits, &models.Commit{
|
||||
Sha: conflictedCommitSha,
|
||||
Name: "",
|
||||
Status: models.StatusRebasing,
|
||||
Action: models.ActionConflict,
|
||||
})
|
||||
}
|
||||
|
||||
for _, t := range todos {
|
||||
if t.Command == todo.UpdateRef {
|
||||
t.Msg = strings.TrimPrefix(t.Ref, "refs/heads/")
|
||||
@@ -328,6 +361,93 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
|
||||
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/done"))
|
||||
if err != nil {
|
||||
self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
doneTodos, err := todo.Parse(bytes.NewBuffer(bytesContent), self.config.GetCoreCommentChar())
|
||||
if err != nil {
|
||||
self.Log.Error(fmt.Sprintf("error occurred while parsing rebase-merge/done file: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
amendFileExists := false
|
||||
if _, err := os.Stat(filepath.Join(self.dotGitDir, "rebase-merge/amend")); err == nil {
|
||||
amendFileExists = true
|
||||
}
|
||||
|
||||
return self.getConflictedCommitImpl(todos, doneTodos, amendFileExists)
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getConflictedCommitImpl(todos []todo.Todo, doneTodos []todo.Todo, amendFileExists bool) string {
|
||||
// Should never be possible, but just to be safe:
|
||||
if len(doneTodos) == 0 {
|
||||
self.Log.Error("no done entries in rebase-merge/done file")
|
||||
return ""
|
||||
}
|
||||
lastTodo := doneTodos[len(doneTodos)-1]
|
||||
if lastTodo.Command == todo.Break || lastTodo.Command == todo.Exec || lastTodo.Command == todo.Reword {
|
||||
return ""
|
||||
}
|
||||
|
||||
// In certain cases, git reschedules commands that failed. One example is if
|
||||
// a patch would overwrite an untracked file (another one is an "exec" that
|
||||
// failed, but we don't care about that here because we dealt with exec
|
||||
// already above). To detect this, compare the last command of the "done"
|
||||
// file against the first command of "git-rebase-todo"; if they are the
|
||||
// same, the command was rescheduled.
|
||||
if len(doneTodos) > 0 && len(todos) > 0 && doneTodos[len(doneTodos)-1] == todos[0] {
|
||||
// Command was rescheduled, no need to display it
|
||||
return ""
|
||||
}
|
||||
|
||||
// Older versions of git have a bug whereby, if a command is rescheduled,
|
||||
// the last successful command is appended to the "done" file again. To
|
||||
// detect this, we need to compare the second-to-last done entry against the
|
||||
// first todo entry, and also compare the last done entry against the
|
||||
// last-but-two done entry; this latter check is needed for the following
|
||||
// case:
|
||||
// pick A
|
||||
// exec make test
|
||||
// pick B
|
||||
// exec make test
|
||||
// If pick B fails with conflicts, then the "done" file contains
|
||||
// pick A
|
||||
// exec make test
|
||||
// pick B
|
||||
// and git-rebase-todo contains
|
||||
// exec make test
|
||||
// Without the last condition we would erroneously treat this as the exec
|
||||
// command being rescheduled, so we wouldn't display our fake entry for
|
||||
// "pick B".
|
||||
if len(doneTodos) >= 3 && len(todos) > 0 && doneTodos[len(doneTodos)-2] == todos[0] &&
|
||||
doneTodos[len(doneTodos)-1] == doneTodos[len(doneTodos)-3] {
|
||||
// Command was rescheduled, no need to display it
|
||||
return ""
|
||||
}
|
||||
|
||||
if lastTodo.Command == todo.Edit {
|
||||
if amendFileExists {
|
||||
// Special case for "edit": if the "amend" file exists, the "edit"
|
||||
// command was successful, otherwise it wasn't
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// I don't think this is ever possible, but again, just to be safe:
|
||||
if lastTodo.Commit == "" {
|
||||
self.Log.Error("last command in rebase-merge/done file doesn't have a commit")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Any other todo that has a commit associated with it must have failed with
|
||||
// a conflict, otherwise we wouldn't have stopped the rebase:
|
||||
return lastTodo.Commit
|
||||
}
|
||||
|
||||
// assuming the file starts like this:
|
||||
// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
|
||||
// From: Lazygit Tester <test@example.com>
|
||||
@@ -392,13 +512,33 @@ func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
func (self *CommitLoader) getExistingMainBranches() []string {
|
||||
return lo.FilterMap(self.UserConfig.Git.MainBranches,
|
||||
func(branchName string, _ int) (string, bool) {
|
||||
ref := "refs/heads/" + branchName
|
||||
// Try to determine upstream of local main branch
|
||||
if ref, err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
|
||||
).DontLog().RunWithOutput(); err == nil {
|
||||
return strings.TrimSpace(ref), true
|
||||
}
|
||||
|
||||
// If this failed, a local branch for this main branch doesn't exist or it
|
||||
// has no upstream configured. Try looking for one in the "origin" remote.
|
||||
ref := "refs/remotes/origin/" + branchName
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
|
||||
).DontLog().Run(); err != nil {
|
||||
return "", false
|
||||
).DontLog().Run(); err == nil {
|
||||
return ref, true
|
||||
}
|
||||
return ref, true
|
||||
|
||||
// If this failed as well, try if we have the main branch as a local
|
||||
// branch. This covers the case where somebody is using git locally
|
||||
// for something, but never pushing anywhere.
|
||||
ref = "refs/heads/" + branchName
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
|
||||
).DontLog().Run(); err == nil {
|
||||
return ref, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -451,4 +591,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s`
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
@@ -13,16 +14,16 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode
|
||||
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com| (origin/better-tests)|e94e8fc5b6fab4cb755f|fix logging
|
||||
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com||d8084cd558925eb7c9c3|refactor
|
||||
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode
|
||||
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|fix logging
|
||||
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|refactor
|
||||
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|WIP
|
||||
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|WIP
|
||||
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|WIP
|
||||
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
|
||||
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
|
||||
|
||||
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
|
||||
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
|
||||
|
||||
func TestGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -44,7 +45,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -56,7 +57,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -66,17 +67,21 @@ func TestGetCommits(t *testing.T) {
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main"},
|
||||
mainBranches: []string{"master", "main", "develop"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", nil). // this one does
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")). // this one doesn't
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches have an upstream
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
|
||||
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/heads/master"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/main"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
@@ -112,8 +117,8 @@ func TestGetCommits(t *testing.T) {
|
||||
Name: "refactor",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
Tags: []string{"123", "456"},
|
||||
ExtraInfo: "(tag: 123, tag: 456)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
AuthorEmail: "jessedduffield@gmail.com",
|
||||
UnixTimestamp: 1640823749,
|
||||
@@ -204,9 +209,13 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist; neither does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
@@ -237,14 +246,16 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/1.0-hotfixes"}, "", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil).
|
||||
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/heads/master", "refs/heads/develop", "refs/heads/1.0-hotfixes"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/develop", "refs/remotes/origin/1.0-hotfixes"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
@@ -271,7 +282,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -283,7 +294,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -319,3 +330,179 @@ func TestGetCommits(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
todos []todo.Todo
|
||||
doneTodos []todo.Todo
|
||||
amendFileExists bool
|
||||
expectedSha string
|
||||
}{
|
||||
{
|
||||
testName: "no done todos",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "common case (conflict)",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "deadbeef",
|
||||
},
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "fa1afe1",
|
||||
},
|
||||
{
|
||||
testName: "last command was 'break'",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{Command: todo.Break},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "last command was 'exec'",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Exec,
|
||||
ExecCommand: "make test",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "last command was 'reword'",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{Command: todo.Reword},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "'pick' was rescheduled",
|
||||
todos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "'pick' was rescheduled, buggy git version",
|
||||
todos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "deadbeaf",
|
||||
},
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "deadbeaf",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "conflicting 'pick' after 'exec'",
|
||||
todos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Exec,
|
||||
ExecCommand: "make test",
|
||||
},
|
||||
},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "deadbeaf",
|
||||
},
|
||||
{
|
||||
Command: todo.Exec,
|
||||
ExecCommand: "make test",
|
||||
},
|
||||
{
|
||||
Command: todo.Pick,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "fa1afe1",
|
||||
},
|
||||
{
|
||||
testName: "'edit' with amend file",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Edit,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
amendFileExists: true,
|
||||
expectedSha: "",
|
||||
},
|
||||
{
|
||||
testName: "'edit' without amend file",
|
||||
todos: []todo.Todo{},
|
||||
doneTodos: []todo.Todo{
|
||||
{
|
||||
Command: todo.Edit,
|
||||
Commit: "fa1afe1",
|
||||
},
|
||||
},
|
||||
amendFileExists: false,
|
||||
expectedSha: "fa1afe1",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
common := utils.NewDummyCommon()
|
||||
|
||||
builder := &CommitLoader{
|
||||
Common: common,
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
|
||||
getRebaseMode: func() (enums.RebaseMode, error) { return enums.REBASE_MODE_INTERACTIVE, nil },
|
||||
dotGitDir: ".git",
|
||||
readFile: func(filename string) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
},
|
||||
walkFiles: func(root string, fn filepath.WalkFunc) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
sha := builder.getConflictedCommitImpl(scenario.todos, scenario.doneTodos, scenario.amendFileExists)
|
||||
assert.Equal(t, scenario.expectedSha, sha)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
configSignoff bool
|
||||
configVerbose string
|
||||
expected []string
|
||||
}
|
||||
|
||||
@@ -122,33 +121,13 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
|
||||
{
|
||||
testName: "Commit using editor",
|
||||
configSignoff: false,
|
||||
configVerbose: "default",
|
||||
expected: []string{"commit"},
|
||||
},
|
||||
{
|
||||
testName: "Commit with --no-verbose flag",
|
||||
configSignoff: false,
|
||||
configVerbose: "never",
|
||||
expected: []string{"commit", "--no-verbose"},
|
||||
},
|
||||
{
|
||||
testName: "Commit with --verbose flag",
|
||||
configSignoff: false,
|
||||
configVerbose: "always",
|
||||
expected: []string{"commit", "--verbose"},
|
||||
},
|
||||
{
|
||||
testName: "Commit with --signoff",
|
||||
configSignoff: true,
|
||||
configVerbose: "default",
|
||||
expected: []string{"commit", "--signoff"},
|
||||
},
|
||||
{
|
||||
testName: "Commit with --signoff and --no-verbose",
|
||||
configSignoff: true,
|
||||
configVerbose: "never",
|
||||
expected: []string{"commit", "--signoff", "--no-verbose"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
@@ -156,7 +135,6 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.Commit.SignOff = s.configSignoff
|
||||
userConfig.Git.Commit.Verbose = s.configVerbose
|
||||
|
||||
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
|
||||
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
|
||||
@@ -212,28 +190,28 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
filterPath: "",
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "-p", "1234567890"},
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
|
||||
},
|
||||
{
|
||||
testName: "Default case with filter path",
|
||||
filterPath: "file.txt",
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "-p", "1234567890", "--", "file.txt"},
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
filterPath: "",
|
||||
contextSize: 77,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "-p", "1234567890"},
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff, ignoring whitespace",
|
||||
filterPath: "",
|
||||
contextSize: 77,
|
||||
ignoreWhitespace: true,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "-p", "1234567890", "--ignore-all-space"},
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -99,3 +99,11 @@ func (self *ConfigCommands) Branches() (map[string]*config.Branch, error) {
|
||||
func (self *ConfigCommands) GetGitFlowPrefixes() string {
|
||||
return self.gitConfig.GetGeneral("--local --get-regexp gitflow.prefix")
|
||||
}
|
||||
|
||||
func (self *ConfigCommands) GetCoreCommentChar() byte {
|
||||
if commentCharStr := self.gitConfig.Get("core.commentChar"); len(commentCharStr) == 1 {
|
||||
return commentCharStr[0]
|
||||
}
|
||||
|
||||
return '#'
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
||||
|
||||
func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error {
|
||||
if stash {
|
||||
if err := self.stash.Save(self.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
|
||||
if err := self.stash.Push(self.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,19 +243,19 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
|
||||
return utils.EditRebaseTodo(
|
||||
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action)
|
||||
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action)
|
||||
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action)
|
||||
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
|
||||
@@ -2,6 +2,8 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type RemoteCommands struct {
|
||||
@@ -46,12 +48,12 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
|
||||
@@ -52,9 +52,9 @@ func (self *StashCommands) Apply(index int) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// Save save stash
|
||||
func (self *StashCommands) Save(message string) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("save", message).
|
||||
// Push push stash
|
||||
func (self *StashCommands) Push(message string) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("push", "-m", message).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -63,8 +63,9 @@ func (self *StashCommands) Save(message string) error {
|
||||
func (self *StashCommands) Store(sha string, message string) error {
|
||||
trimmedMessage := strings.Trim(message, " \t")
|
||||
|
||||
cmdArgs := NewGitCmd("stash").Arg("store", sha).
|
||||
cmdArgs := NewGitCmd("stash").Arg("store").
|
||||
ArgIf(trimmedMessage != "", "-m", trimmedMessage).
|
||||
Arg(sha).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -93,7 +94,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashAndKeepIndex(message string) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("save", message, "--keep-index").
|
||||
cmdArgs := NewGitCmd("stash").Arg("push", "--keep-index", "-m", message).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -107,7 +108,7 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
|
||||
).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.Save(message); err != nil {
|
||||
if err := self.Push(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -129,7 +130,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.Save(message); err != nil {
|
||||
if err := self.Push(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
|
||||
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("stash").Arg("save", message, "--include-untracked").
|
||||
NewGitCmd("stash").Arg("push", "--include-untracked", "-m", message).
|
||||
ToArgv(),
|
||||
).Run()
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ func TestStashPop(t *testing.T) {
|
||||
|
||||
func TestStashSave(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "save", "A stash message"}, "", nil)
|
||||
ExpectGitArgs([]string{"stash", "push", "-m", "A stash message"}, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Save("A stash message"))
|
||||
assert.NoError(t, instance.Push("A stash message"))
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestStashStore(t *testing.T) {
|
||||
testName: "Non-empty message",
|
||||
sha: "0123456789abcdef",
|
||||
message: "New stash name",
|
||||
expected: []string{"stash", "store", "0123456789abcdef", "-m", "New stash name"},
|
||||
expected: []string{"stash", "store", "-m", "New stash name", "0123456789abcdef"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
@@ -162,7 +162,7 @@ func TestStashRename(t *testing.T) {
|
||||
expectedShaCmd: []string{"rev-parse", "refs/stash@{3}"},
|
||||
shaResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd", "-m", "New message"},
|
||||
expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
|
||||
},
|
||||
{
|
||||
testName: "Empty message",
|
||||
|
||||
@@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
@@ -23,7 +24,7 @@ type PushOpts struct {
|
||||
SetUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
|
||||
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
||||
}
|
||||
@@ -35,12 +36,12 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
|
||||
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
|
||||
return cmdObj, nil
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Push(opts PushOpts) error {
|
||||
cmdObj, err := self.PushCmdObj(opts)
|
||||
func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error {
|
||||
cmdObj, err := self.PushCmdObj(task, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -48,28 +49,33 @@ func (self *SyncCommands) Push(opts PushOpts) error {
|
||||
return cmdObj.Run()
|
||||
}
|
||||
|
||||
type FetchOptions struct {
|
||||
Background bool
|
||||
}
|
||||
|
||||
// Fetch fetch git repo
|
||||
func (self *SyncCommands) FetchCmdObj(opts FetchOptions) oscommands.ICmdObj {
|
||||
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
ArgIf(self.UserConfig.Git.FetchAll, "--all").
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
if opts.Background {
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
} else {
|
||||
cmdObj.PromptOnCredentialRequest()
|
||||
}
|
||||
return cmdObj.WithMutex(self.syncMutex)
|
||||
cmdObj.PromptOnCredentialRequest(task)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
||||
cmdObj := self.FetchCmdObj(opts)
|
||||
return cmdObj.Run()
|
||||
func (self *SyncCommands) Fetch(task gocui.Task) error {
|
||||
return self.FetchCmdObj(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
ArgIf(self.UserConfig.Git.FetchAll, "--all").
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
cmdObj.WithMutex(self.syncMutex)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackground() error {
|
||||
return self.FetchBackgroundCmdObj().Run()
|
||||
}
|
||||
|
||||
type PullOptions struct {
|
||||
@@ -78,7 +84,7 @@ type PullOptions struct {
|
||||
FastForwardOnly bool
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
cmdArgs := NewGitCmd("pull").
|
||||
Arg("--no-edit").
|
||||
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||
@@ -88,22 +94,22 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
// has 'pull.rebase = interactive' configured.
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||
func (self *SyncCommands) FastForward(task gocui.Task, branchName string, remoteName string, remoteBranchName string) error {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
Arg(remoteName).
|
||||
Arg(remoteBranchName + ":" + branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
||||
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
Arg(remoteName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package git_commands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -88,7 +89,8 @@ func TestSyncPush(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
s.test(instance.PushCmdObj(s.opts))
|
||||
task := gocui.NewFakeTask()
|
||||
s.test(instance.PushCmdObj(task, s.opts))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -96,7 +98,6 @@ func TestSyncPush(t *testing.T) {
|
||||
func TestSyncFetch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
opts FetchOptions
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
}
|
||||
@@ -104,7 +105,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in foreground (all=false)",
|
||||
opts: FetchOptions{Background: false},
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
@@ -114,7 +114,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Fetch in foreground (all=true)",
|
||||
opts: FetchOptions{Background: false},
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
@@ -122,9 +121,29 @@ func TestSyncFetch(t *testing.T) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all"})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
task := gocui.NewFakeTask()
|
||||
s.test(instance.FetchCmdObj(task))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncFetchBackground(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in background (all=false)",
|
||||
opts: FetchOptions{Background: true},
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
@@ -134,7 +153,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Fetch in background (all=true)",
|
||||
opts: FetchOptions{Background: true},
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
@@ -149,7 +167,7 @@ func TestSyncFetch(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
s.test(instance.FetchCmdObj(s.opts))
|
||||
s.test(instance.FetchBackgroundCmdObj())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
type TagCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
@@ -34,9 +36,9 @@ func (self *TagCommands) Delete(tagName string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
||||
func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string) error {
|
||||
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package git_config
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -20,6 +21,7 @@ type CachedGitConfig struct {
|
||||
cache map[string]string
|
||||
runGitConfigCmd func(*exec.Cmd) (string, error)
|
||||
log *logrus.Entry
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewStdCachedGitConfig(log *logrus.Entry) *CachedGitConfig {
|
||||
@@ -31,10 +33,14 @@ func NewCachedGitConfig(runGitConfigCmd func(*exec.Cmd) (string, error), log *lo
|
||||
cache: make(map[string]string),
|
||||
runGitConfigCmd: runGitConfigCmd,
|
||||
log: log,
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CachedGitConfig) Get(key string) string {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[key]; ok {
|
||||
self.log.Debugf("using cache for key " + key)
|
||||
return value
|
||||
@@ -46,6 +52,9 @@ func (self *CachedGitConfig) Get(key string) string {
|
||||
}
|
||||
|
||||
func (self *CachedGitConfig) GetGeneral(args string) string {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[args]; ok {
|
||||
self.log.Debugf("using cache for args " + args)
|
||||
return value
|
||||
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
// Conveniently for us, the todo package starts the enum at 1, and given
|
||||
// that it doesn't have a "none" value, we're setting ours to 0
|
||||
ActionNone todo.TodoCommand = 0
|
||||
// "Comment" is the last one of the todo package's enum entries
|
||||
ActionConflict = todo.Comment + 1
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
|
||||
@@ -15,3 +15,15 @@ func (f *CommitFile) ID() string {
|
||||
func (f *CommitFile) Description() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f *CommitFile) Added() bool {
|
||||
return f.ChangeStatus == "A"
|
||||
}
|
||||
|
||||
func (f *CommitFile) Deleted() bool {
|
||||
return f.ChangeStatus == "D"
|
||||
}
|
||||
|
||||
func (f *CommitFile) GetPath() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
@@ -56,13 +57,16 @@ type ICmdObj interface {
|
||||
// returns true if IgnoreEmptyError() was called
|
||||
ShouldIgnoreEmptyError() bool
|
||||
|
||||
PromptOnCredentialRequest() ICmdObj
|
||||
PromptOnCredentialRequest(task gocui.Task) ICmdObj
|
||||
FailOnCredentialRequest() ICmdObj
|
||||
|
||||
WithMutex(mutex *deadlock.Mutex) ICmdObj
|
||||
Mutex() *deadlock.Mutex
|
||||
|
||||
GetCredentialStrategy() CredentialStrategy
|
||||
GetTask() gocui.Task
|
||||
|
||||
Clone() ICmdObj
|
||||
}
|
||||
|
||||
type CmdObj struct {
|
||||
@@ -85,6 +89,7 @@ type CmdObj struct {
|
||||
|
||||
// if set to true, it means we might be asked to enter a username/password by this command.
|
||||
credentialStrategy CredentialStrategy
|
||||
task gocui.Task
|
||||
|
||||
// can be set so that we don't run certain commands simultaneously
|
||||
mutex *deadlock.Mutex
|
||||
@@ -192,8 +197,9 @@ func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) e
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
func (self *CmdObj) PromptOnCredentialRequest() ICmdObj {
|
||||
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) ICmdObj {
|
||||
self.credentialStrategy = PROMPT
|
||||
self.task = task
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -207,3 +213,21 @@ func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
|
||||
func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
|
||||
return self.credentialStrategy
|
||||
}
|
||||
|
||||
func (self *CmdObj) GetTask() gocui.Task {
|
||||
return self.task
|
||||
}
|
||||
|
||||
func (self *CmdObj) Clone() ICmdObj {
|
||||
clone := &CmdObj{}
|
||||
*clone = *self
|
||||
clone.cmd = cloneCmd(self.cmd)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneCmd(cmd *exec.Cmd) *exec.Cmd {
|
||||
clone := &exec.Cmd{}
|
||||
*clone = *cmd
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -19,15 +20,6 @@ type ICmdObjRunner interface {
|
||||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
Password CredentialType = iota
|
||||
Username
|
||||
Passphrase
|
||||
PIN
|
||||
)
|
||||
|
||||
type cmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
guiIO *guiIO
|
||||
@@ -182,26 +174,6 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Whenever we're asked for a password we just enter a newline, which will
|
||||
// eventually cause the command to fail.
|
||||
var failPromptFn = func(CredentialType) string { return "\n" }
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
var promptFn func(CredentialType) string
|
||||
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
promptFn = self.guiIO.promptForCredentialFn
|
||||
case FAIL:
|
||||
promptFn = failPromptFn
|
||||
case NONE:
|
||||
// we should never land here
|
||||
return errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
|
||||
}
|
||||
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) {
|
||||
self.guiIO.logCommandFn(cmdObj.ToString(), true)
|
||||
}
|
||||
@@ -233,25 +205,6 @@ func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
|
||||
})
|
||||
}
|
||||
|
||||
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
|
||||
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
|
||||
func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
cmdObj ICmdObj,
|
||||
promptUserForCredential func(CredentialType) string,
|
||||
) error {
|
||||
// setting the output to english so we can parse it for a username/password request
|
||||
cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runAndStreamAux(
|
||||
cmdObj ICmdObj,
|
||||
onRun func(*cmdHandler, io.Writer),
|
||||
@@ -296,13 +249,79 @@ func (self *cmdObjRunner) runAndStreamAux(
|
||||
if cmdObj.ShouldIgnoreEmptyError() {
|
||||
return nil
|
||||
}
|
||||
return errors.New(stdout.String())
|
||||
stdoutStr := stdout.String()
|
||||
if stdoutStr != "" {
|
||||
return errors.New(stdoutStr)
|
||||
}
|
||||
return errors.New("Command exited with non-zero exit code, but no output")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) {
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
Password CredentialType = iota
|
||||
Username
|
||||
Passphrase
|
||||
PIN
|
||||
)
|
||||
|
||||
// Whenever we're asked for a password we just enter a newline, which will
|
||||
// eventually cause the command to fail.
|
||||
var failPromptFn = func(CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
ch <- "\n"
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
promptFn, err := self.getCredentialPromptFn(cmdObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(CredentialType) <-chan string, error) {
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
return self.guiIO.promptForCredentialFn, nil
|
||||
case FAIL:
|
||||
return failPromptFn, nil
|
||||
default:
|
||||
// we should never land here
|
||||
return nil, errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
|
||||
}
|
||||
}
|
||||
|
||||
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
|
||||
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
|
||||
func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
cmdObj ICmdObj,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
) error {
|
||||
// setting the output to english so we can parse it for a username/password request
|
||||
cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj.GetTask())
|
||||
})
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) processOutput(
|
||||
reader io.Reader,
|
||||
writer io.Writer,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
task gocui.Task,
|
||||
) {
|
||||
checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
@@ -311,7 +330,14 @@ func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, prom
|
||||
newBytes := scanner.Bytes()
|
||||
askFor, ok := checkForCredentialRequest(newBytes)
|
||||
if ok {
|
||||
toInput := promptUserForCredential(askFor)
|
||||
responseChan := promptUserForCredential(askFor)
|
||||
if task != nil {
|
||||
task.Pause()
|
||||
}
|
||||
toInput := <-responseChan
|
||||
if task != nil {
|
||||
task.Continue()
|
||||
}
|
||||
// If the return data is empty we don't write anything to stdin
|
||||
if toInput != "" {
|
||||
_, _ = writer.Write([]byte(toInput))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,18 @@ func getRunner() *cmdObjRunner {
|
||||
}
|
||||
}
|
||||
|
||||
func toChanFn(f func(ct CredentialType) string) func(CredentialType) <-chan string {
|
||||
return func(ct CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
|
||||
go func() {
|
||||
ch <- f(ct)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessOutput(t *testing.T) {
|
||||
defaultPromptUserForCredential := func(ct CredentialType) string {
|
||||
switch ct {
|
||||
@@ -99,7 +112,8 @@ func TestProcessOutput(t *testing.T) {
|
||||
reader := strings.NewReader(scenario.output)
|
||||
writer := &strings.Builder{}
|
||||
|
||||
runner.processOutput(reader, writer, scenario.promptUserForCredential)
|
||||
task := gocui.NewFakeTask()
|
||||
runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), task)
|
||||
|
||||
if writer.String() != scenario.expectedToWrite {
|
||||
t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String())
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package oscommands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func TestCmdObjToString(t *testing.T) {
|
||||
@@ -31,3 +34,20 @@ func TestCmdObjToString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
task := gocui.NewFakeTask()
|
||||
cmdObj := &CmdObj{task: task, cmd: &exec.Cmd{}}
|
||||
clone := cmdObj.Clone()
|
||||
if clone == cmdObj {
|
||||
t.Errorf("Clone should not return the same object")
|
||||
}
|
||||
|
||||
if clone.GetTask() == nil {
|
||||
t.Errorf("Clone task should not be nil")
|
||||
}
|
||||
|
||||
if clone.GetTask() != task {
|
||||
t.Errorf("Clone should have the same task")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@ import (
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
@@ -55,21 +55,21 @@ func CopyFile(src, dst string) (err error) {
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
return //nolint: nakedret
|
||||
@@ -92,7 +92,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
if err == nil {
|
||||
// it exists so let's remove it
|
||||
@@ -103,12 +103,12 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -118,7 +118,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
@@ -128,7 +128,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,15 @@ type guiIO struct {
|
||||
// this allows us to request info from the user like username/password, in the event
|
||||
// that a command requests it.
|
||||
// the 'credential' arg is something like 'username' or 'password'
|
||||
promptForCredentialFn func(credential CredentialType) string
|
||||
promptForCredentialFn func(credential CredentialType) <-chan string
|
||||
}
|
||||
|
||||
func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn func() io.Writer, promptForCredentialFn func(CredentialType) string) *guiIO {
|
||||
func NewGuiIO(
|
||||
log *logrus.Entry,
|
||||
logCommandFn func(string, bool),
|
||||
newCmdWriterFn func() io.Writer,
|
||||
promptForCredentialFn func(CredentialType) <-chan string,
|
||||
) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: logCommandFn,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/OpenPeeDeeP/xdg"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -156,6 +157,11 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err = migrateUserConfig(path, content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(content, base); err != nil {
|
||||
return nil, fmt.Errorf("The config at `%s` couldn't be parsed, please inspect it before opening up an issue.\n%w", path, err)
|
||||
}
|
||||
@@ -164,6 +170,30 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// Do any backward-compatibility migrations of things that have changed in the
|
||||
// config over time; examples are renaming a key to a better name, moving a key
|
||||
// from one container to another, or changing the type of a key (e.g. from bool
|
||||
// to an enum).
|
||||
func migrateUserConfig(path string, content []byte) ([]byte, error) {
|
||||
changedContent, err := yaml_utils.RenameYamlKey(content, []string{"gui", "skipUnstageLineWarning"},
|
||||
"skipDiscardChangeWarning")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
// Add more migrations here...
|
||||
|
||||
// Write config back if changed
|
||||
if string(changedContent) != string(content) {
|
||||
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
|
||||
return nil, fmt.Errorf("Couldn't write migrated config back to `%s`: %s", path, err)
|
||||
}
|
||||
return changedContent, nil
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetDebug() bool {
|
||||
return c.Debug
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kakoune"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
editTemplate: "hx -- {{filename}}",
|
||||
editAtLineTemplate: "hx -- {{filename}}:{{line}}",
|
||||
|
||||
+49
-42
@@ -32,7 +32,7 @@ type GuiConfig struct {
|
||||
ScrollHeight int `yaml:"scrollHeight"`
|
||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
@@ -49,6 +49,7 @@ type GuiConfig struct {
|
||||
ShowCommandLog bool `yaml:"showCommandLog"`
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion"`
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
@@ -59,15 +60,16 @@ type GuiConfig struct {
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
}
|
||||
|
||||
type CommitLengthConfig struct {
|
||||
@@ -101,8 +103,7 @@ type PagingConfig struct {
|
||||
}
|
||||
|
||||
type CommitConfig struct {
|
||||
SignOff bool `yaml:"signOff"`
|
||||
Verbose string `yaml:"verbose"`
|
||||
SignOff bool `yaml:"signOff"`
|
||||
}
|
||||
|
||||
type MergingConfig struct {
|
||||
@@ -347,16 +348,21 @@ type OSConfig struct {
|
||||
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
|
||||
}
|
||||
|
||||
type CustomCommandAfterHook struct {
|
||||
CheckForConflicts bool `yaml:"checkForConflicts"`
|
||||
}
|
||||
|
||||
type CustomCommand struct {
|
||||
Key string `yaml:"key"`
|
||||
Context string `yaml:"context"`
|
||||
Command string `yaml:"command"`
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
Description string `yaml:"description"`
|
||||
Stream bool `yaml:"stream"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
Key string `yaml:"key"`
|
||||
Context string `yaml:"context"`
|
||||
Command string `yaml:"command"`
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
Description string `yaml:"description"`
|
||||
Stream bool `yaml:"stream"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
@@ -396,27 +402,28 @@ type CustomCommandMenuOption struct {
|
||||
func GetDefaultConfig() *UserConfig {
|
||||
return &UserConfig{
|
||||
Gui: GuiConfig{
|
||||
ScrollHeight: 2,
|
||||
ScrollPastBottom: true,
|
||||
MouseEvents: true,
|
||||
SkipUnstageLineWarning: false,
|
||||
SkipStashWarning: false,
|
||||
SidePanelWidth: 0.3333,
|
||||
ExpandFocusedSidePanel: false,
|
||||
MainPanelSplitMode: "flexible",
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
ScrollHeight: 2,
|
||||
ScrollPastBottom: true,
|
||||
MouseEvents: true,
|
||||
SkipDiscardChangeWarning: false,
|
||||
SkipStashWarning: false,
|
||||
SidePanelWidth: 0.3333,
|
||||
ExpandFocusedSidePanel: false,
|
||||
MainPanelSplitMode: "flexible",
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
Theme: ThemeConfig{
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
SearchingActiveBorderColor: []string{"cyan", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
},
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
@@ -426,6 +433,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
ExperimentalShowBranchHeads: false,
|
||||
ShowBranchCommitHash: false,
|
||||
CommandLogSize: 8,
|
||||
@@ -441,7 +449,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
},
|
||||
Commit: CommitConfig{
|
||||
SignOff: false,
|
||||
Verbose: "default",
|
||||
},
|
||||
Merging: MergingConfig{
|
||||
ManualCommit: false,
|
||||
|
||||
+23
-10
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -15,11 +15,11 @@ type BackgroundRoutineMgr struct {
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
pauseBackgroundThreads bool
|
||||
pauseBackgroundRefreshes bool
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
|
||||
self.pauseBackgroundThreads = pause
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
|
||||
self.pauseBackgroundRefreshes = pause
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
@@ -39,9 +39,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
go utils.Safe(func() { self.startBackgroundFilesRefresh(refreshInterval) })
|
||||
} else {
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
@@ -52,6 +50,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
if !isNew {
|
||||
@@ -69,17 +68,31 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
done := make(chan struct{})
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if self.pauseBackgroundThreads {
|
||||
if self.pauseBackgroundRefreshes {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
self.gui.c.OnWorker(func(gocui.Task) {
|
||||
_ = function()
|
||||
done <- struct{}{}
|
||||
})
|
||||
// waiting so that we don't bunch up refreshes if the refresh takes longer than the interval
|
||||
<-done
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
@@ -88,7 +101,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
err = self.gui.git.Sync.FetchBackground()
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
|
||||
@@ -135,10 +135,6 @@ func (gui *Gui) getRandomTip() string {
|
||||
"To escape a mode, for example cherry-picking, patch-building, diffing, or filtering mode, you can just spam the '%s' button. Unless of course you have `quitOnTopLevelReturn` enabled in your config",
|
||||
formattedKey(config.Universal.Return),
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"To search for a string in your panel, press '%s'",
|
||||
formattedKey(config.Universal.StartSearch),
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"You can page through the items of a panel using '%s' and '%s'",
|
||||
formattedKey(config.Universal.PrevPage),
|
||||
|
||||
+29
-3
@@ -200,9 +200,9 @@ func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
|
||||
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
if err := self.gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
if opts.NewContextKey != context.SEARCH_CONTEXT_KEY {
|
||||
if c.GetKind() == types.MAIN_CONTEXT || c.GetKind() == types.TEMPORARY_POPUP {
|
||||
self.gui.helpers.Search.CancelSearchIfSearching(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,8 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
return err
|
||||
}
|
||||
|
||||
self.gui.helpers.Search.RenderSearchStatus(c)
|
||||
|
||||
desiredTitle := c.Title()
|
||||
if desiredTitle != "" {
|
||||
v.Title = desiredTitle
|
||||
@@ -326,6 +328,30 @@ func (self *ContextMgr) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllFilterable() []types.IFilterableContext {
|
||||
var result []types.IFilterableContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if ctx, ok := context.(types.IFilterableContext); ok {
|
||||
result = append(result, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllSearchable() []types.ISearchableContext {
|
||||
var result []types.ISearchableContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if ctx, ok := context.(types.ISearchableContext); ok {
|
||||
result = append(result, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// all list contexts
|
||||
func (self *ContextMgr) AllList() []types.IListContext {
|
||||
var listContexts []types.IListContext
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type BranchesContext struct {
|
||||
*BasicViewModel[*models.Branch]
|
||||
*FilteredListViewModel[*models.Branch]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@@ -17,11 +17,16 @@ var (
|
||||
)
|
||||
|
||||
func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Branch { return c.Model().Branches },
|
||||
func(branch *models.Branch) []string {
|
||||
return []string{branch.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
c.Model().Branches,
|
||||
viewModel.GetItems(),
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Tr,
|
||||
@@ -30,7 +35,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
}
|
||||
|
||||
self := &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Branches,
|
||||
|
||||
@@ -13,6 +13,7 @@ type CommitFilesContext struct {
|
||||
*filetree.CommitFileTreeViewModel
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -38,9 +39,10 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
})
|
||||
}
|
||||
|
||||
return &CommitFilesContext{
|
||||
ctx := &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
@@ -57,6 +59,13 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type FilteredList[T any] struct {
|
||||
filteredIndices []int // if nil, we are not filtering
|
||||
|
||||
getList func() []T
|
||||
getFilterFields func(T) []string
|
||||
filter string
|
||||
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] {
|
||||
return &FilteredList[T]{
|
||||
getList: getList,
|
||||
getFilterFields: getFilterFields,
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) GetFilter() string {
|
||||
return self.filter
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) SetFilter(filter string) {
|
||||
self.filter = filter
|
||||
|
||||
self.applyFilter()
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) ClearFilter() {
|
||||
self.SetFilter("")
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) IsFiltering() bool {
|
||||
return self.filter != ""
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) GetFilteredList() []T {
|
||||
if self.filteredIndices == nil {
|
||||
return self.getList()
|
||||
}
|
||||
return utils.ValuesAtIndices(self.getList(), self.filteredIndices)
|
||||
}
|
||||
|
||||
// TODO: update to just 'Len'
|
||||
func (self *FilteredList[T]) UnfilteredLen() int {
|
||||
return len(self.getList())
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) applyFilter() {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if self.filter == "" {
|
||||
self.filteredIndices = nil
|
||||
} else {
|
||||
self.filteredIndices = []int{}
|
||||
for i, item := range self.getList() {
|
||||
for _, field := range self.getFilterFields(item) {
|
||||
if self.match(field, self.filter) {
|
||||
self.filteredIndices = append(self.filteredIndices, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) match(haystack string, needle string) bool {
|
||||
return utils.CaseAwareContains(haystack, needle)
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) UnfilteredIndex(index int) int {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if self.filteredIndices == nil {
|
||||
return index
|
||||
}
|
||||
|
||||
// we use -1 when there are no items
|
||||
if index == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return self.filteredIndices[index]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package context
|
||||
|
||||
type FilteredListViewModel[T any] struct {
|
||||
*FilteredList[T]
|
||||
*ListViewModel[T]
|
||||
}
|
||||
|
||||
func NewFilteredListViewModel[T any](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] {
|
||||
filteredList := NewFilteredList(getList, getFilterFields)
|
||||
|
||||
self := &FilteredListViewModel[T]{
|
||||
FilteredList: filteredList,
|
||||
}
|
||||
|
||||
listViewModel := NewListViewModel(filteredList.GetFilteredList)
|
||||
|
||||
self.ListViewModel = listViewModel
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *FilteredListViewModel[T]) IsFilterableContext() {}
|
||||
|
||||
func (self *FilteredListViewModel[T]) ClearFilter() {
|
||||
// Set the selected line index to the unfiltered index of the currently selected line,
|
||||
// so that the current item is still selected after the filter is cleared.
|
||||
unfilteredIndex := self.FilteredList.UnfilteredIndex(self.GetSelectedLineIdx())
|
||||
|
||||
self.FilteredList.ClearFilter()
|
||||
|
||||
self.SetSelectedLineIdx(unfilteredIndex)
|
||||
}
|
||||
@@ -31,7 +31,14 @@ func (self *ListContextTrait) GetList() types.IList {
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) FocusLine() {
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
// Doing this at the end of the layout function because we need the view to be
|
||||
// resized before we focus the line, otherwise if we're in accordion mode
|
||||
// the view could be squashed and won't how to adjust the cursor/origin
|
||||
self.c.AfterLayout(func() error {
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
return nil
|
||||
})
|
||||
|
||||
self.setFooter()
|
||||
|
||||
if self.refreshViewportOnChange {
|
||||
|
||||
@@ -2,13 +2,13 @@ package context
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
|
||||
type BasicViewModel[T any] struct {
|
||||
type ListViewModel[T any] struct {
|
||||
*traits.ListCursor
|
||||
getModel func() []T
|
||||
}
|
||||
|
||||
func NewBasicViewModel[T any](getModel func() []T) *BasicViewModel[T] {
|
||||
self := &BasicViewModel[T]{
|
||||
func NewListViewModel[T any](getModel func() []T) *ListViewModel[T] {
|
||||
self := &ListViewModel[T]{
|
||||
getModel: getModel,
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ func NewBasicViewModel[T any](getModel func() []T) *BasicViewModel[T] {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BasicViewModel[T]) Len() int {
|
||||
func (self *ListViewModel[T]) Len() int {
|
||||
return len(self.getModel())
|
||||
}
|
||||
|
||||
func (self *BasicViewModel[T]) GetSelected() T {
|
||||
func (self *ListViewModel[T]) GetSelected() T {
|
||||
if self.Len() == 0 {
|
||||
return Zero[T]()
|
||||
}
|
||||
@@ -29,6 +29,10 @@ func (self *BasicViewModel[T]) GetSelected() T {
|
||||
return self.getModel()[self.GetSelectedLineIdx()]
|
||||
}
|
||||
|
||||
func (self *ListViewModel[T]) GetItems() []T {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func Zero[T any]() T {
|
||||
return *new(T)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
type LocalCommitsContext struct {
|
||||
*LocalCommitsViewModel
|
||||
*ListContextTrait
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -57,8 +58,9 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
)
|
||||
}
|
||||
|
||||
return &LocalCommitsContext{
|
||||
ctx := &LocalCommitsContext{
|
||||
LocalCommitsViewModel: viewModel,
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Commits,
|
||||
@@ -73,6 +75,13 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetSelectedItemId() string {
|
||||
@@ -85,7 +94,7 @@ func (self *LocalCommitsContext) GetSelectedItemId() string {
|
||||
}
|
||||
|
||||
type LocalCommitsViewModel struct {
|
||||
*BasicViewModel[*models.Commit]
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
// If this is true we limit the amount of commits we load, for the sake of keeping things fast.
|
||||
// If the user attempts to scroll past the end of the list, we will load more commits.
|
||||
@@ -97,7 +106,7 @@ type LocalCommitsViewModel struct {
|
||||
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
ListViewModel: NewListViewModel(getModel),
|
||||
limitCommits: true,
|
||||
showWholeGitGraph: c.UserConfig.Git.Log.ShowWholeGraph,
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (self *MenuContext) GetSelectedItemId() string {
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
*BasicViewModel[*types.MenuItem]
|
||||
*FilteredListViewModel[*types.MenuItem]
|
||||
}
|
||||
|
||||
func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
@@ -65,7 +65,10 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
c: c,
|
||||
}
|
||||
|
||||
self.BasicViewModel = NewBasicViewModel(func() []*types.MenuItem { return self.menuItems })
|
||||
self.FilteredListViewModel = NewFilteredListViewModel(
|
||||
func() []*types.MenuItem { return self.menuItems },
|
||||
func(item *types.MenuItem) []string { return item.LabelColumns },
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -76,11 +79,12 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) {
|
||||
|
||||
// TODO: move into presentation package
|
||||
func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]string {
|
||||
showKeys := slices.Some(self.menuItems, func(item *types.MenuItem) bool {
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
showKeys := slices.Some(menuItems, func(item *types.MenuItem) bool {
|
||||
return item.Key != nil
|
||||
})
|
||||
|
||||
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
|
||||
return slices.Map(menuItems, func(item *types.MenuItem) []string {
|
||||
displayStrings := item.LabelColumns
|
||||
|
||||
if !showKeys {
|
||||
@@ -93,6 +97,7 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
self.c.UserConfig.Keybinding.Universal.Confirm,
|
||||
self.c.UserConfig.Keybinding.Universal.Select,
|
||||
self.c.UserConfig.Keybinding.Universal.Return,
|
||||
self.c.UserConfig.Keybinding.Universal.StartSearch,
|
||||
}
|
||||
keyLabel := keybindings.LabelFromKey(item.Key)
|
||||
keyStyle := style.FgCyan
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
type PatchExplorerContext struct {
|
||||
*SimpleContext
|
||||
*SearchTrait
|
||||
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
@@ -28,7 +29,7 @@ func NewPatchExplorerContext(
|
||||
|
||||
c *ContextCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
ctx := &PatchExplorerContext{
|
||||
state: nil,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
@@ -42,7 +43,18 @@ func NewPatchExplorerContext(
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
|
||||
func(selectedLineIdx int) error {
|
||||
ctx.GetMutex().Lock()
|
||||
defer ctx.GetMutex().Unlock()
|
||||
return ctx.NavigateTo(ctx.c.IsCurrentContext(ctx), selectedLineIdx)
|
||||
}),
|
||||
)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type ReflogCommitsContext struct {
|
||||
*BasicViewModel[*models.Commit]
|
||||
*FilteredListViewModel[*models.Commit]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@@ -19,11 +19,16 @@ var (
|
||||
)
|
||||
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Commit { return c.Model().FilteredReflogCommits },
|
||||
func(commit *models.Commit) []string {
|
||||
return []string{commit.ShortSha(), commit.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetReflogCommitListDisplayStrings(
|
||||
c.Model().FilteredReflogCommits,
|
||||
viewModel.GetItems(),
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
@@ -35,7 +40,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
}
|
||||
|
||||
return &ReflogCommitsContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().ReflogCommits,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RemoteBranchesContext struct {
|
||||
*BasicViewModel[*models.RemoteBranch]
|
||||
*FilteredListViewModel[*models.RemoteBranch]
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
@@ -20,15 +20,20 @@ var (
|
||||
func NewRemoteBranchesContext(
|
||||
c *ContextCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.RemoteBranch { return c.Model().RemoteBranches },
|
||||
func(remoteBranch *models.RemoteBranch) []string {
|
||||
return []string{remoteBranch.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
|
||||
return presentation.GetRemoteBranchListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemoteBranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
FilteredListViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().RemoteBranches,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type RemotesContext struct {
|
||||
*BasicViewModel[*models.Remote]
|
||||
*FilteredListViewModel[*models.Remote]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@@ -17,14 +17,19 @@ var (
|
||||
)
|
||||
|
||||
func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Remote { return c.Model().Remotes },
|
||||
func(remote *models.Remote) []string {
|
||||
return []string{remote.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
|
||||
return presentation.GetRemoteListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemotesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Remotes,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
type SearchTrait struct {
|
||||
c *ContextCommon
|
||||
|
||||
searchString string
|
||||
}
|
||||
|
||||
func NewSearchTrait(c *ContextCommon) *SearchTrait {
|
||||
return &SearchTrait{c: c}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) GetSearchString() string {
|
||||
return self.searchString
|
||||
}
|
||||
|
||||
func (self *SearchTrait) SetSearchString(searchString string) {
|
||||
self.searchString = searchString
|
||||
}
|
||||
|
||||
func (self *SearchTrait) ClearSearchString() {
|
||||
self.SetSearchString("")
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *SearchTrait) IsSearchableContext() {}
|
||||
|
||||
func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.NoMatchesFor,
|
||||
self.searchString,
|
||||
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.MatchesFor,
|
||||
self.searchString,
|
||||
index+1,
|
||||
total,
|
||||
theme.OptionsFgColor.Sprintf(
|
||||
self.c.Tr.SearchKeybindings,
|
||||
keybindings.Label(keybindingConfig.Universal.NextMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.PrevMatch),
|
||||
keybindings.Label(keybindingConfig.Universal.Return),
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) IsSearching() bool {
|
||||
return self.searchString != ""
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type StashContext struct {
|
||||
*BasicViewModel[*models.StashEntry]
|
||||
*FilteredListViewModel[*models.StashEntry]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@@ -19,14 +19,19 @@ var (
|
||||
func NewStashContext(
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.StashEntry { return c.Model().StashEntries },
|
||||
func(stashEntry *models.StashEntry) []string {
|
||||
return []string{stashEntry.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
|
||||
return presentation.GetStashEntryListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &StashContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Stash,
|
||||
|
||||
@@ -12,9 +12,12 @@ import (
|
||||
)
|
||||
|
||||
type SubCommitsContext struct {
|
||||
c *ContextCommon
|
||||
|
||||
*SubCommitsViewModel
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -26,7 +29,7 @@ func NewSubCommitsContext(
|
||||
c *ContextCommon,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(
|
||||
ListViewModel: NewListViewModel(
|
||||
func() []*models.Commit { return c.Model().SubCommits },
|
||||
),
|
||||
ref: nil,
|
||||
@@ -60,8 +63,10 @@ func NewSubCommitsContext(
|
||||
)
|
||||
}
|
||||
|
||||
return &SubCommitsContext{
|
||||
ctx := &SubCommitsContext{
|
||||
c: c,
|
||||
SubCommitsViewModel: viewModel,
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@@ -78,12 +83,19 @@ func NewSubCommitsContext(
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
type SubCommitsViewModel struct {
|
||||
// name of the ref that the sub-commits are shown for
|
||||
ref types.Ref
|
||||
*BasicViewModel[*models.Commit]
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
limitCommits bool
|
||||
}
|
||||
|
||||
@@ -7,21 +7,26 @@ import (
|
||||
)
|
||||
|
||||
type SubmodulesContext struct {
|
||||
*BasicViewModel[*models.SubmoduleConfig]
|
||||
*FilteredListViewModel[*models.SubmoduleConfig]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SubmodulesContext)(nil)
|
||||
|
||||
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.SubmoduleConfig { return c.Model().Submodules },
|
||||
func(submodule *models.SubmoduleConfig) []string {
|
||||
return []string{submodule.Name}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
|
||||
return presentation.GetSubmoduleListDisplayStrings(viewModel.GetItems())
|
||||
}
|
||||
|
||||
return &SubmodulesContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Submodules,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type SuggestionsContext struct {
|
||||
*BasicViewModel[*types.Suggestion]
|
||||
*ListViewModel[*types.Suggestion]
|
||||
*ListContextTrait
|
||||
|
||||
State *SuggestionsContextState
|
||||
@@ -30,7 +30,7 @@ func NewSuggestionsContext(
|
||||
c *ContextCommon,
|
||||
) *SuggestionsContext {
|
||||
state := &SuggestionsContextState{
|
||||
AsyncHandler: tasks.NewAsyncHandler(),
|
||||
AsyncHandler: tasks.NewAsyncHandler(c.OnWorker),
|
||||
}
|
||||
getModel := func() []*types.Suggestion {
|
||||
return state.Suggestions
|
||||
@@ -40,11 +40,11 @@ func NewSuggestionsContext(
|
||||
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
|
||||
}
|
||||
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewListViewModel(getModel)
|
||||
|
||||
return &SuggestionsContext{
|
||||
State: state,
|
||||
BasicViewModel: viewModel,
|
||||
State: state,
|
||||
ListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Suggestions,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type TagsContext struct {
|
||||
*BasicViewModel[*models.Tag]
|
||||
*FilteredListViewModel[*models.Tag]
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
@@ -19,14 +19,19 @@ var (
|
||||
func NewTagsContext(
|
||||
c *ContextCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
|
||||
viewModel := NewFilteredListViewModel(
|
||||
func() []*models.Tag { return c.Model().Tags },
|
||||
func(tag *models.Tag) []string {
|
||||
return []string{tag.Name, tag.Message}
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
|
||||
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
BasicViewModel: viewModel,
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Tags,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type WorkingTreeContext struct {
|
||||
*filetree.FileTreeViewModel
|
||||
*ListContextTrait
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
@@ -29,7 +30,8 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
})
|
||||
}
|
||||
|
||||
return &WorkingTreeContext{
|
||||
ctx := &WorkingTreeContext{
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@@ -44,6 +46,13 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
|
||||
ctx.GetList().SetSelectedLineIdx(selectedLineIdx)
|
||||
return ctx.HandleFocus(types.OnFocusOpts{})
|
||||
}))
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *WorkingTreeContext) GetSelectedItemId() string {
|
||||
|
||||
@@ -99,6 +99,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@@ -162,6 +163,16 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
|
||||
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
||||
|
||||
filterControllerFactory := controllers.NewFilterControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllFilterable() {
|
||||
controllers.AttachControllers(context, filterControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
searchControllerFactory := controllers.NewSearchControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllSearchable() {
|
||||
controllers.AttachControllers(context, searchControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
// allow for navigating between side window contexts
|
||||
for _, context := range []types.Context{
|
||||
gui.State.Contexts.Status,
|
||||
@@ -323,6 +334,10 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
suggestionsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Search,
|
||||
controllers.NewSearchPromptController(common),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Global,
|
||||
syncController,
|
||||
undoController,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -363,11 +364,12 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func() error {
|
||||
return self.c.WithLoaderPanel(message, func(task gocui.Task) error {
|
||||
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
task,
|
||||
git_commands.PullOptions{
|
||||
RemoteName: branch.UpstreamRemote,
|
||||
BranchName: branch.UpstreamBranch,
|
||||
@@ -381,7 +383,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
err := self.c.Git().Sync.FastForward(task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -153,15 +153,31 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error {
|
||||
parentContext, ok := self.c.CurrentContext().GetParentContext()
|
||||
if !ok || parentContext.GetKey() != context.LOCAL_COMMITS_CONTEXT_KEY {
|
||||
return self.c.ErrorMsg(self.c.Tr.CanOnlyDiscardFromLocalCommits)
|
||||
}
|
||||
|
||||
if node.File == nil {
|
||||
return self.c.ErrorMsg(self.c.Tr.DiscardNotSupportedForDirectory)
|
||||
}
|
||||
|
||||
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
prompt := self.c.Tr.DiscardFileChangesPrompt
|
||||
if node.File.Added() {
|
||||
prompt = self.c.Tr.DiscardAddedFileChangesPrompt
|
||||
} else if node.File.Deleted() {
|
||||
prompt = self.c.Tr.DiscardDeletedFileChangesPrompt
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardFileChangesTitle,
|
||||
Prompt: self.c.Tr.DiscardFileChangesPrompt,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
|
||||
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
@@ -189,7 +205,7 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
|
||||
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
|
||||
toggle := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -116,7 +117,7 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
@@ -133,7 +134,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() erro
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
||||
@@ -151,7 +152,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
|
||||
}
|
||||
|
||||
pull := func(stash bool) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
|
||||
@@ -181,7 +182,7 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)
|
||||
|
||||
@@ -35,7 +35,7 @@ func (self *DiffingMenuAction) Call() error {
|
||||
Label: self.c.Tr.EnterRefToDiff,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.EnteRefName,
|
||||
Title: self.c.Tr.EnterRefName,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
@@ -648,7 +647,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.ResetCommitFilterState,
|
||||
Label: self.c.Tr.ResetFilter,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayAll)
|
||||
},
|
||||
@@ -658,7 +657,7 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||
self.context().FileTreeViewModel.SetFilter(filter)
|
||||
self.context().FileTreeViewModel.SetStatusFilter(filter)
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
}
|
||||
|
||||
@@ -698,7 +697,7 @@ func (self *FilesController) createStashMenu() error {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
@@ -741,7 +740,7 @@ func (self *FilesController) createStashMenu() error {
|
||||
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
}
|
||||
// ordinary stash
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
@@ -750,7 +749,7 @@ func (self *FilesController) createStashMenu() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) stash() error {
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
|
||||
}
|
||||
|
||||
func (self *FilesController) createResetToUpstreamMenu() error {
|
||||
@@ -801,17 +800,17 @@ func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) e
|
||||
}
|
||||
|
||||
func (self *FilesController) fetch() error {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func() error {
|
||||
if err := self.fetchAux(); err != nil {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func(task gocui.Task) error {
|
||||
if err := self.fetchAux(task); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FilesController) fetchAux() (err error) {
|
||||
func (self *FilesController) fetchAux(task gocui.Task) (err error) {
|
||||
self.c.LogAction("Fetch")
|
||||
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
|
||||
err = self.c.Git().Sync.Fetch(task)
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
@@ -145,7 +146,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
}
|
||||
|
||||
func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
|
||||
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type FilterControllerFactory struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewFilterControllerFactory(c *ControllerCommon) *FilterControllerFactory {
|
||||
return &FilterControllerFactory{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilterControllerFactory) Create(context types.IFilterableContext) *FilterController {
|
||||
return &FilterController{
|
||||
baseController: baseController{},
|
||||
c: self.c,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
type FilterController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
|
||||
context types.IFilterableContext
|
||||
}
|
||||
|
||||
func (self *FilterController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *FilterController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: self.OpenFilterPrompt,
|
||||
Description: self.c.Tr.StartFilter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilterController) OpenFilterPrompt() error {
|
||||
return self.c.Helpers().Search.OpenFilterPrompt(self.context)
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package helpers
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type AppStatusHelper struct {
|
||||
@@ -27,12 +27,12 @@ func (self *AppStatusHelper) Toast(message string) {
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
||||
go utils.Safe(func() {
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) {
|
||||
self.statusMgr().WithWaitingStatus(message, func() {
|
||||
self.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
if err := f(task); err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
@@ -50,7 +50,7 @@ func (self *AppStatusHelper) GetStatusString() string {
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@@ -75,7 +76,7 @@ func (self *CherryPickHelper) Paste() error {
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: self.c.Tr.SureCherryPick,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CherryPick)
|
||||
err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
|
||||
return self.rebaseHelper.CheckMergeOrRebase(err)
|
||||
|
||||
@@ -292,7 +292,9 @@ func (self *ConfirmationHelper) ResizePopupPanel(v *gocui.View, content string)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeMenu() {
|
||||
itemCount := self.c.Contexts().Menu.GetList().Len()
|
||||
// we want the unfiltered length here so that if we're filtering we don't
|
||||
// resize the window
|
||||
itemCount := self.c.Contexts().Menu.UnfilteredLen()
|
||||
offset := 3
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -20,11 +18,11 @@ func NewCredentialsHelper(
|
||||
}
|
||||
|
||||
// promptUserForCredential wait for a username, password or passphrase input from the credentials popup
|
||||
func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.CredentialType) string {
|
||||
waitGroup := sync.WaitGroup{}
|
||||
waitGroup.Add(1)
|
||||
|
||||
userInput := ""
|
||||
// We return a channel rather than returning the string directly so that the calling function knows
|
||||
// when the prompt has been created (before the user has entered anything) so that it can
|
||||
// note that we're now waiting on user input and lazygit isn't processing anything.
|
||||
func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
title, mask := self.getTitleAndMask(passOrUname)
|
||||
@@ -33,24 +31,19 @@ func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.Cr
|
||||
Title: title,
|
||||
Mask: mask,
|
||||
HandleConfirm: func(input string) error {
|
||||
userInput = input
|
||||
|
||||
waitGroup.Done()
|
||||
ch <- input + "\n"
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
HandleClose: func() error {
|
||||
waitGroup.Done()
|
||||
ch <- "\n"
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// wait for username/passwords/passphrase input
|
||||
waitGroup.Wait()
|
||||
|
||||
return userInput + "\n"
|
||||
return ch
|
||||
}
|
||||
|
||||
func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.CredentialType) (string, bool) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -41,7 +42,7 @@ func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus
|
||||
}
|
||||
|
||||
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
|
||||
if err := cmdObj.StreamOutput().Run(); err != nil {
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return self.c.Error(
|
||||
|
||||
@@ -46,6 +46,7 @@ type Helpers struct {
|
||||
Mode *ModeHelper
|
||||
AppStatus *AppStatusHelper
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
Search *SearchHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
@@ -78,5 +79,6 @@ func NewStubHelpers() *Helpers {
|
||||
Mode: &ModeHelper{},
|
||||
AppStatus: &AppStatusHelper{},
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
Search: &SearchHelper{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,33 +137,47 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
|
||||
} else if strings.Contains(result.Error(), "No rebase in progress?") {
|
||||
// assume in this case that we're already done
|
||||
return nil
|
||||
} else if isMergeConflictErr(result.Error()) {
|
||||
mode := self.workingTreeStateNoun()
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.FoundConflictsTitle,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.ViewConflictsMenuItem,
|
||||
OnPress: func() error {
|
||||
return self.c.PushContext(self.c.Contexts().Files)
|
||||
},
|
||||
Key: 'v',
|
||||
},
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode),
|
||||
OnPress: func() error {
|
||||
return self.genericMergeCommand(REBASE_OPTION_ABORT)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
},
|
||||
HideCancel: true,
|
||||
})
|
||||
} else {
|
||||
return self.CheckForConflicts(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error {
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isMergeConflictErr(result.Error()) {
|
||||
return self.PromptForConflictHandling()
|
||||
} else {
|
||||
return self.c.ErrorMsg(result.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) PromptForConflictHandling() error {
|
||||
mode := self.workingTreeStateNoun()
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.FoundConflictsTitle,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.ViewConflictsMenuItem,
|
||||
OnPress: func() error {
|
||||
return self.c.PushContext(self.c.Contexts().Files)
|
||||
},
|
||||
Key: 'v',
|
||||
},
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode),
|
||||
OnPress: func() error {
|
||||
return self.genericMergeCommand(REBASE_OPTION_ABORT)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
},
|
||||
HideCancel: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error {
|
||||
// prompt user to confirm that they want to abort, then do it
|
||||
mode := self.workingTreeStateNoun()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
@@ -63,8 +64,6 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
f := func() {
|
||||
var scopeSet *set.Set[types.RefreshableView]
|
||||
if len(options.Scope) == 0 {
|
||||
@@ -87,15 +86,13 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
}
|
||||
|
||||
refresh := func(f func()) {
|
||||
wg.Add(1)
|
||||
func() {
|
||||
if options.Mode == types.ASYNC {
|
||||
go utils.Safe(f)
|
||||
} else {
|
||||
if options.Mode == types.ASYNC {
|
||||
self.c.OnWorker(func(t gocui.Task) {
|
||||
f()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
})
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
@@ -143,8 +140,6 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
refresh(func() { _ = self.mergeConflictsHelper.RefreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
self.refreshStatus()
|
||||
|
||||
if options.Then != nil {
|
||||
@@ -206,7 +201,7 @@ func getModeName(mode types.RefreshMode) string {
|
||||
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
switch self.c.State().GetRepoState().GetStartupStage() {
|
||||
case types.INITIAL:
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
_ = self.refreshReflogCommits()
|
||||
self.refreshBranches()
|
||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||
@@ -350,6 +345,9 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
// self.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a State.Model.Branches array to pick the current branch from
|
||||
func (self *RefreshHelper) refreshBranches() {
|
||||
self.c.Mutexes().RefreshingBranchesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
|
||||
|
||||
reflogCommits := self.c.Model().FilteredReflogCommits
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
// in filter mode we filter our reflog commits to just those containing the path
|
||||
@@ -464,10 +462,10 @@ func (self *RefreshHelper) refreshStateFiles() error {
|
||||
// I'd prefer to maintain as little state as possible.
|
||||
if conflictFileCount > 0 {
|
||||
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted)
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayAll)
|
||||
}
|
||||
|
||||
self.c.Model().Files = files
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -50,7 +51,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
|
||||
if err := self.c.Git().Branch.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
|
||||
|
||||
@@ -64,7 +65,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
Title: self.c.Tr.AutoStashTitle,
|
||||
Prompt: self.c.Tr.AutoStashPrompt,
|
||||
HandleConfirm: func() error {
|
||||
if err := self.c.Git().Stash.Save(self.c.Tr.StashPrefix + ref); err != nil {
|
||||
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + ref); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// NOTE: this helper supports both filtering and searching. Filtering is when
|
||||
// the contents of the list are filtered, whereas searching does not actually
|
||||
// change the contents of the list but instead just highlights the search.
|
||||
// The general term we use to capture both searching and filtering is...
|
||||
// 'searching', which is unfortunate but I can't think of a better name.
|
||||
|
||||
type SearchHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewSearchHelper(
|
||||
c *HelperCommon,
|
||||
) *SearchHelper {
|
||||
return &SearchHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
||||
promptView := self.promptView()
|
||||
promptView.ClearTextArea()
|
||||
promptView.TextArea.TypeString(context.GetFilter())
|
||||
promptView.RenderTextArea()
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OpenSearchPrompt(context types.ISearchableContext) error {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
searchString := context.GetSearchString()
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
||||
promptView := self.promptView()
|
||||
promptView.ClearTextArea()
|
||||
promptView.TextArea.TypeString(searchString)
|
||||
promptView.RenderTextArea()
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().Search); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext) {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
searchString := context.GetFilter()
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
|
||||
|
||||
promptView := self.promptView()
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return)))
|
||||
}
|
||||
|
||||
func (self *SearchHelper) DisplaySearchStatus(context types.ISearchableContext) {
|
||||
state := self.searchState()
|
||||
|
||||
state.Context = context
|
||||
|
||||
self.searchPrefixView().SetContent(self.c.Tr.SearchPrefix)
|
||||
_ = context.GetView().SelectCurrentSearchResult()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) searchState() *types.SearchState {
|
||||
return self.c.State().GetRepoState().GetSearchState()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) searchPrefixView() *gocui.View {
|
||||
return self.c.Views().SearchPrefix
|
||||
}
|
||||
|
||||
func (self *SearchHelper) promptView() *gocui.View {
|
||||
return self.c.Contexts().Search.GetView()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) promptContent() string {
|
||||
return self.c.Contexts().Search.GetView().TextArea.GetContent()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) Confirm() error {
|
||||
state := self.searchState()
|
||||
if self.promptContent() == "" {
|
||||
return self.CancelPrompt()
|
||||
}
|
||||
|
||||
switch state.SearchType() {
|
||||
case types.SearchTypeFilter:
|
||||
return self.ConfirmFilter()
|
||||
case types.SearchTypeSearch:
|
||||
return self.ConfirmSearch()
|
||||
case types.SearchTypeNone:
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) ConfirmFilter() error {
|
||||
// We also do this on each keypress but we do it here again just in case
|
||||
state := self.searchState()
|
||||
|
||||
_, ok := state.Context.(types.IFilterableContext)
|
||||
if !ok {
|
||||
self.c.Log.Warnf("Context %s is not filterable", state.Context.GetKey())
|
||||
return nil
|
||||
}
|
||||
|
||||
self.OnPromptContentChanged(self.promptContent())
|
||||
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) ConfirmSearch() error {
|
||||
state := self.searchState()
|
||||
|
||||
context, ok := state.Context.(types.ISearchableContext)
|
||||
if !ok {
|
||||
self.c.Log.Warnf("Context %s is searchable", state.Context.GetKey())
|
||||
return nil
|
||||
}
|
||||
|
||||
searchString := self.promptContent()
|
||||
context.SetSearchString(searchString)
|
||||
|
||||
view := context.GetView()
|
||||
|
||||
if err := self.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := view.Search(searchString); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) CancelPrompt() error {
|
||||
self.Cancel()
|
||||
|
||||
return self.c.PopContext()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) Cancel() {
|
||||
state := self.searchState()
|
||||
|
||||
switch context := state.Context.(type) {
|
||||
case types.IFilterableContext:
|
||||
context.ClearFilter()
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
context.ClearSearchString()
|
||||
context.GetView().ClearSearch()
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
self.HidePrompt()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) OnPromptContentChanged(searchString string) {
|
||||
state := self.searchState()
|
||||
switch context := state.Context.(type) {
|
||||
case types.IFilterableContext:
|
||||
context.SetSelectedLineIdx(0)
|
||||
_ = context.GetView().SetOriginY(0)
|
||||
context.SetFilter(searchString)
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
// do nothing
|
||||
default:
|
||||
// do nothing (shouldn't land here)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) RenderSearchStatus(c types.Context) {
|
||||
if c.GetKey() == context.SEARCH_CONTEXT_KEY {
|
||||
return
|
||||
}
|
||||
|
||||
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
||||
if searchableContext.IsSearching() {
|
||||
self.setSearchingFrameColor()
|
||||
self.DisplaySearchStatus(searchableContext)
|
||||
return
|
||||
}
|
||||
}
|
||||
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
||||
if filterableContext.IsFiltering() {
|
||||
self.setSearchingFrameColor()
|
||||
self.DisplayFilterStatus(filterableContext)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.HidePrompt()
|
||||
}
|
||||
|
||||
func (self *SearchHelper) CancelSearchIfSearching(c types.Context) {
|
||||
if searchableContext, ok := c.(types.ISearchableContext); ok {
|
||||
view := searchableContext.GetView()
|
||||
if view != nil && view.IsSearching() {
|
||||
view.ClearSearch()
|
||||
searchableContext.ClearSearchString()
|
||||
self.Cancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if filterableContext, ok := c.(types.IFilterableContext); ok {
|
||||
if filterableContext.IsFiltering() {
|
||||
filterableContext.ClearFilter()
|
||||
self.Cancel()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHelper) HidePrompt() {
|
||||
self.setNonSearchingFrameColor()
|
||||
|
||||
state := self.searchState()
|
||||
state.Context = nil
|
||||
}
|
||||
|
||||
func (self *SearchHelper) setSearchingFrameColor() {
|
||||
self.c.GocuiGui().SelFgColor = theme.SearchingActiveBorderColor
|
||||
self.c.GocuiGui().SelFrameColor = theme.SearchingActiveBorderColor
|
||||
}
|
||||
|
||||
func (self *SearchHelper) setNonSearchingFrameColor() {
|
||||
self.c.GocuiGui().SelFgColor = theme.ActiveBorderColor
|
||||
self.c.GocuiGui().SelFrameColor = theme.ActiveBorderColor
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@@ -100,7 +101,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
|
||||
// Notably, unlike other suggestion functions we're not showing all the options
|
||||
// if nothing has been typed because there'll be too much to display efficiently
|
||||
func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func() error {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(gocui.Task) error {
|
||||
trie := patricia.NewTrie()
|
||||
// load every non-gitignored file in the repo
|
||||
ignore, err := gitignore.FromGit()
|
||||
@@ -157,6 +158,12 @@ func (self *SuggestionsHelper) getTagNames() []string {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetTagsSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
tagNames := self.getTagNames()
|
||||
|
||||
return FuzzySearchFunc(tagNames)
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
remoteBranchNames := self.getRemoteBranchNames("/")
|
||||
localBranchNames := self.getBranchNames()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -37,7 +38,7 @@ func (self *UpdateHelper) CheckForUpdateInBackground() {
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func(gocui.Task) error {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
@@ -53,7 +54,7 @@ func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) startUpdating(newVersion string) {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func() error {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func(gocui.Task) error {
|
||||
self.c.State().SetUpdating(true)
|
||||
err := self.updater.Update(newVersion)
|
||||
return self.onUpdateFinish(err)
|
||||
|
||||
@@ -55,7 +55,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
self.c.Modes().Filtering.Active()
|
||||
|
||||
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
|
||||
self.c.State().GetRepoState().IsSearching() ||
|
||||
self.c.State().GetRepoState().InSearchPrompt() ||
|
||||
self.modeHelper.IsAnyModeActive() ||
|
||||
self.appStatusHelper.HasStatus()
|
||||
infoSectionSize := 0
|
||||
@@ -174,11 +174,17 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if self.c.State().GetRepoState().IsSearching() {
|
||||
if self.c.State().GetRepoState().InSearchPrompt() {
|
||||
var prefix string
|
||||
if self.c.State().GetRepoState().GetSearchState().SearchType() == types.SearchTypeSearch {
|
||||
prefix = self.c.Tr.SearchPrefix
|
||||
} else {
|
||||
prefix = self.c.Tr.FilterPrefix
|
||||
}
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(self.c.Tr.SearchPrefix),
|
||||
Size: runewidth.StringWidth(prefix),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
|
||||
@@ -150,18 +150,7 @@ func (self *ListController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoTop), Handler: self.HandleGotoTop, Description: self.c.Tr.GotoTop},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollLeft), Handler: self.HandleScrollLeft},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.ScrollRight), Handler: self.HandleScrollRight},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: func() error { self.c.OpenSearch(); return nil },
|
||||
Description: self.c.Tr.StartSearch,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.GotoBottom),
|
||||
Description: self.c.Tr.GotoBottom,
|
||||
Handler: self.HandleGotoBottom,
|
||||
Tag: "navigation",
|
||||
},
|
||||
{Tag: "navigation", Key: opts.GetKey(opts.Config.Universal.GotoBottom), Handler: self.HandleGotoBottom, Description: self.c.Tr.GotoBottom},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -217,7 +218,7 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
Title: self.c.Tr.Squash,
|
||||
Prompt: self.c.Tr.SureSquashThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
||||
return self.interactiveRebase(todo.Squash)
|
||||
})
|
||||
@@ -242,7 +243,7 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
Title: self.c.Tr.Fixup,
|
||||
Prompt: self.c.Tr.SureFixupThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup)
|
||||
})
|
||||
@@ -338,7 +339,7 @@ func (self *LocalCommitsController) drop(commit *models.Commit) error {
|
||||
Title: self.c.Tr.DeleteCommitTitle,
|
||||
Prompt: self.c.Tr.DeleteCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
return self.interactiveRebase(todo.Drop)
|
||||
})
|
||||
@@ -355,7 +356,7 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
err := self.c.Git().Rebase.EditRebase(commit.Sha)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@@ -385,6 +386,10 @@ func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand) e
|
||||
// commit meaning you are trying to edit the todo file rather than actually
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
func (self *LocalCommitsController) handleMidRebaseCommand(action todo.TodoCommand, commit *models.Commit) (bool, error) {
|
||||
if commit.Action == models.ActionConflict {
|
||||
return true, self.c.ErrorMsg(self.c.Tr.ChangingThisActionIsNotAllowed)
|
||||
}
|
||||
|
||||
if !commit.IsTODO() {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
// If we are in a rebase, the only action that is allowed for
|
||||
@@ -434,7 +439,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
if commit.IsTODO() {
|
||||
if !commits[index+1].IsTODO() {
|
||||
if !commits[index+1].IsTODO() || commits[index+1].Action == models.ActionConflict {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -456,7 +461,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index)
|
||||
if err == nil {
|
||||
@@ -494,7 +499,7 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
||||
err := self.c.Git().Rebase.MoveCommitUp(self.c.Model().Commits, index)
|
||||
if err == nil {
|
||||
@@ -520,7 +525,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@@ -554,7 +559,7 @@ func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) resetAuthor() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
|
||||
return self.c.Error(err)
|
||||
@@ -569,7 +574,7 @@ func (self *LocalCommitsController) setAuthor() error {
|
||||
Title: self.c.Tr.SetAuthorPromptTitle,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
HandleConfirm: func(value string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
|
||||
return self.c.Error(err)
|
||||
@@ -667,7 +672,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
|
||||
Title: self.c.Tr.SquashAboveCommits,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@@ -689,9 +694,7 @@ func (self *LocalCommitsController) openSearch() error {
|
||||
}
|
||||
}
|
||||
|
||||
self.c.OpenSearch()
|
||||
|
||||
return nil
|
||||
return self.c.Helpers().Search.OpenSearchPrompt(self.context())
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) gotoBottom() error {
|
||||
@@ -721,7 +724,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
self.context().SetLimitCommits(false)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
|
||||
return self.c.Refresh(
|
||||
types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}},
|
||||
)
|
||||
@@ -764,7 +767,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
onPress := func(value string) func() error {
|
||||
return func() error {
|
||||
self.c.UserConfig.Git.Log.Order = value
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
|
||||
return self.c.Refresh(
|
||||
types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
@@ -814,7 +817,7 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
context := self.context()
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -123,12 +123,6 @@ func (self *PatchExplorerController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.ScrollRight),
|
||||
Handler: self.withRenderAndFocus(self.HandleScrollRight),
|
||||
},
|
||||
{
|
||||
Tag: "navigation",
|
||||
Key: opts.GetKey(opts.Config.Universal.StartSearch),
|
||||
Handler: func() error { self.c.OpenSearch(); return nil },
|
||||
Description: self.c.Tr.StartSearch,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
||||
Handler: self.withLock(self.CopySelectedToClipboard),
|
||||
|
||||
@@ -50,6 +50,19 @@ func (self *QuitActions) confirmQuitDuringUpdate() error {
|
||||
func (self *QuitActions) Escape() error {
|
||||
currentContext := self.c.CurrentContext()
|
||||
|
||||
switch ctx := currentContext.(type) {
|
||||
case types.IFilterableContext:
|
||||
if ctx.IsFiltering() {
|
||||
self.c.Helpers().Search.Cancel()
|
||||
return nil
|
||||
}
|
||||
case types.ISearchableContext:
|
||||
if ctx.IsSearching() {
|
||||
self.c.Helpers().Search.Cancel()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
parentContext, hasParent := currentContext.GetParentContext()
|
||||
if hasParent && currentContext != nil && parentContext != nil {
|
||||
// TODO: think about whether this should be marked as a return rather than adding to the stack
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@@ -59,11 +60,6 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Handler: self.checkSelected(self.setAsUpstream),
|
||||
Description: self.c.Tr.SetAsUpstream,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.escape,
|
||||
Description: self.c.Tr.ReturnToRemotesList,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
|
||||
Handler: self.checkSelected(self.createResetMenu),
|
||||
@@ -115,10 +111,6 @@ func (self *RemoteBranchesController) checkSelected(callback func(*models.Remote
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) escape() error {
|
||||
return self.c.PushContext(self.c.Contexts().Remotes)
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch) error {
|
||||
message := fmt.Sprintf("%s '%s'?", self.c.Tr.DeleteRemoteBranchMessage, selectedBranch.FullName())
|
||||
|
||||
@@ -126,9 +118,9 @@ func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch
|
||||
Title: self.c.Tr.DeleteRemoteBranch,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
err := self.c.Git().Remote.DeleteRemoteBranch(selectedBranch.RemoteName, selectedBranch.Name)
|
||||
err := self.c.Git().Remote.DeleteRemoteBranch(task, selectedBranch.RemoteName, selectedBranch.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user